From 0116abdb8f82cb684b578f7a6449c015215357a7 Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 28 Nov 2024 15:46:36 +0100 Subject: [PATCH 01/34] feat: create controller --- .../db/whois/api/rdap/RdapObjectMapper.java | 3 +- .../whois/api/rdap/RdapRelationService.java | 81 +++++++++++++++++++ .../ripe/db/whois/api/rdap/RdapService.java | 65 +++++++++++++-- .../whois/api/rdap/domain/RelationType.java | 18 +++++ 4 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java create mode 100644 whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java index 1743319603..840761d4c9 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java @@ -284,7 +284,8 @@ private RpslObject getRpslObject(final int objectId) { } } - private RdapObject getRdapObject(final String requestUrl, final RpslObject rpslObject, @Nullable final AbuseContact abuseContact) { + private RdapObject getRdapObject(final String requestUrl, final RpslObject rpslObject, + @Nullable final AbuseContact abuseContact) { RdapObject rdapResponse; final ObjectType rpslObjectType = rpslObject.getType(); diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java new file mode 100644 index 0000000000..0a0423d0d4 --- /dev/null +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -0,0 +1,81 @@ +package net.ripe.db.whois.api.rdap; + +import net.ripe.db.whois.api.rdap.domain.RelationType; +import net.ripe.db.whois.common.ip.IpInterval; +import net.ripe.db.whois.common.ip.Ipv4Resource; +import net.ripe.db.whois.common.ip.Ipv6Resource; +import net.ripe.db.whois.common.iptree.IpEntry; +import net.ripe.db.whois.common.iptree.IpTree; +import net.ripe.db.whois.common.iptree.Ipv4DomainTree; +import net.ripe.db.whois.common.iptree.Ipv4Tree; +import net.ripe.db.whois.common.iptree.Ipv6DomainTree; +import net.ripe.db.whois.common.iptree.Ipv6Tree; +import net.ripe.db.whois.common.rpsl.attrs.Domain; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class RdapRelationService { + + private final RdapQueryHandler rdapQueryHandler; + private final Ipv4Tree ip4Tree; + private final Ipv6Tree ip6Tree; + private final Ipv4DomainTree ipv4DomainTree; + private final Ipv6DomainTree ipv6DomainTree; + + public RdapRelationService(final RdapQueryHandler rdapQueryHandler, final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, + final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree) { + this.rdapQueryHandler = rdapQueryHandler; + this.ip4Tree = ip4Tree; + this.ip6Tree = ip6Tree; + this.ipv4DomainTree = ipv4DomainTree; + this.ipv6DomainTree = ipv6DomainTree; + } + + public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ + final Domain domain = Domain.parse(pkey); + final IpInterval reverseIp = domain.getReverseIp(); + + return getRelationPkeys(relationType, getIpDomainTree(reverseIp), reverseIp); + } + + public List getInetnumRelationPkeys(final String pkey, final RelationType relationType){ + final IpInterval ip = IpInterval.parse(pkey); + return getRelationPkeys(relationType, getIpTree(ip), ip); + } + + private List getRelationPkeys(RelationType relationType, IpTree ipTree, IpInterval ip) { + final List ipEntries = getIpEntries(ipTree, relationType, ip); + return ipEntries.stream().map(ipEntry -> ipEntry.getKey().toString()).toList(); + } + + private List getIpEntries(final IpTree ipTree, final RelationType relationType, final IpInterval reverseIp) { + return switch (relationType) { + case UP -> ipTree.findFirstLessSpecific(reverseIp); + case TOP -> List.of((IpEntry) ipTree.findAllLessSpecific(reverseIp).getFirst()); + case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); + case BOTTOM -> ipTree.findAllMoreSpecific(reverseIp); //TODO: [MH] get the MOST specific, can be more than 1 + }; + } + + private IpTree getIpTree(final IpInterval reverseIp) { + if (reverseIp instanceof Ipv4Resource) { + return ip4Tree; + } else if (reverseIp instanceof Ipv6Resource) { + return ip6Tree; + } + + throw new IllegalArgumentException("Unexpected reverse ip: " + reverseIp); + } + + private IpTree getIpDomainTree(final IpInterval reverseIp) { + if (reverseIp instanceof Ipv4Resource) { + return ipv4DomainTree; + } else if (reverseIp instanceof Ipv6Resource) { + return ipv6DomainTree; + } + + throw new IllegalArgumentException("Unexpected reverse ip: " + reverseIp); + } +} diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 4ecef0d38a..a4226c8502 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -15,6 +15,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import net.ripe.db.whois.api.rdap.domain.RdapRequestType; +import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.api.rest.RestServiceHelper; import net.ripe.db.whois.common.dao.RpslObjectInfo; import net.ripe.db.whois.common.dao.RpslObjectUpdateDao; @@ -73,6 +74,7 @@ public class RdapService { private final RdapRequestValidator rdapRequestValidator; private final RpslObjectUpdateDao rpslObjectUpdateDao; private final SourceContext sourceContext; + private final RdapRelationService rdapRelationService; /** * @@ -99,7 +101,8 @@ public RdapService(final RdapQueryHandler rdapQueryHandler, @Value("${rdap.public.baseUrl:}") final String baseUrl, final RdapRequestValidator rdapRequestValidator, @Value("${rdap.search.max.results:100}") final int maxResultSize, - @Value("${rdap.entity.max.results:100}") final int maxEntityResultSize) { + @Value("${rdap.entity.max.results:100}") final int maxEntityResultSize, + final RdapRelationService rdapRelationService) { this.sourceContext = sourceContext; this.rdapQueryHandler = rdapQueryHandler; this.abuseCFinder = abuseCFinder; @@ -112,6 +115,7 @@ public RdapService(final RdapQueryHandler rdapQueryHandler, this.maxResultSize = maxResultSize; this.maxEntityResultSize = maxEntityResultSize; this.rpslObjectUpdateDao = rpslObjectUpdateDao; + this.rdapRelationService = rdapRelationService; } @GET @@ -245,6 +249,52 @@ public Response help(@Context final HttpServletRequest request) { .build(); } + @GET + @Produces({MediaType.APPLICATION_JSON, CONTENT_TYPE_RDAP_JSON}) + @Path("/{objectType}/rirSearch1/{relation}/{key:.*}") + public Response relationSearch( + @Context final HttpServletRequest request, + @PathParam("objectType") RdapRequestType requestType, + @PathParam("relation") RelationType relationType, + @PathParam("key") final String key, + @QueryParam("status") final String status) { + + if (status != null && (relationType.equals(RelationType.DOWN) || relationType.equals(RelationType.BOTTOM))){ + throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); + } + + final Set whoisObjectTypes = requestType.getWhoisObjectTypes(key); + + switch (requestType) { + case AUTNUM -> { + String autnumKey = String.format("AS%s", key); + rdapRequestValidator.validateAutnum(autnumKey); + return lookupForAutNum(request, autnumKey); + } + case DOMAIN -> { + rdapRequestValidator.validateDomain(key); + final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); + + final List rpslObjects = relatedPkeys + .stream() + .flatMap(relatedPkey -> rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(DOMAIN), key), request)) + .toList(); + + return Response.ok(rdapObjectMapper.mapSearch( + getRequestUrl(request), + rpslObjects, + maxResultSize)) + .header(CONTENT_TYPE, CONTENT_TYPE_RDAP_JSON) + .build(); + } + case IP -> { + rdapRequestValidator.validateIp(request.getRequestURI(), key); + return lookupWithRedirectUrl(request, whoisObjectTypes, key); + } + default -> throw new RdapException("400 Bad Request", "Invalid or unknown type" + requestType, HttpStatus.BAD_REQUEST_400); + } + } + private Response lookupWithRedirectUrl(final HttpServletRequest request, final Set objectTypes, final String key) { if (isRedirect(Iterables.getOnlyElement(objectTypes), key)) { return redirect(getRequestPath(request), getQueryObject(objectTypes, key)); @@ -300,7 +350,10 @@ protected Response lookupForDomain(final HttpServletRequest request, final Strin final Stream inetnumResult = rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(INETNUM, INET6NUM), domain.getReverseIp().toString()), request); - return getDomainResponse(request, domainResult, inetnumResult); + return Response.ok( + getDomainEntity(request, domainResult, inetnumResult)) + .header(CONTENT_TYPE, CONTENT_TYPE_RDAP_JSON) + .build(); } protected Response lookupObject(final HttpServletRequest request, final Set objectTypes, final String key) { @@ -374,7 +427,8 @@ private Response getOrganisationResponse(final HttpServletRequest request, .build(); } - private Response getDomainResponse(final HttpServletRequest request, final Stream domainResult, final Stream inetnumResult) { + private Object getDomainEntity(final HttpServletRequest request, final Stream domainResult, + final Stream inetnumResult) { final Iterator domainIterator = domainResult.iterator(); final Iterator inetnumIterator = inetnumResult.iterator(); if (!domainIterator.hasNext()) { @@ -387,10 +441,7 @@ private Response getDomainResponse(final HttpServletRequest request, final Strea throw new RdapException("500 Internal Error", "Unexpected result size: " + Iterators.size(domainIterator), HttpStatus.INTERNAL_SERVER_ERROR_500); } - return Response.ok( - rdapObjectMapper.mapDomainEntity(getRequestUrl(request), domainObject, inetnumObject)) - .header(CONTENT_TYPE, CONTENT_TYPE_RDAP_JSON) - .build(); + return rdapObjectMapper.mapDomainEntity(getRequestUrl(request), domainObject, inetnumObject); } private Response getResponse(final HttpServletRequest request, final Iterable result) { diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java new file mode 100644 index 0000000000..6dfb8a5127 --- /dev/null +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java @@ -0,0 +1,18 @@ +package net.ripe.db.whois.api.rdap.domain; + +public enum RelationType { + UP("up"), + TOP("top"), + DOWN("down"), + BOTTOM("bottom"); + + private final String value; + + RelationType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} From cf6bcc688a401a937c9fbd33076d73b378804bc2 Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 2 Dec 2024 16:33:28 +0100 Subject: [PATCH 02/34] feat: rdap Relation endpoint and ITs --- .../whois/api/rdap/RdapRelationService.java | 32 ++- .../api/rdap/RdapRelationTypeConverter.java | 35 ++++ .../ripe/db/whois/api/rdap/RdapService.java | 64 +++--- .../whois/api/rdap/RdapServletDeployer.java | 8 +- .../api/rdap/domain/RdapRequestType.java | 18 ++ .../api/rdap/RdapServiceTestIntegration.java | 192 ++++++++++++++++++ .../db/whois/common/etree/IntervalMap.java | 16 ++ .../common/etree/MultiValueIntervalMap.java | 5 + .../whois/common/etree/NestedIntervalMap.java | 20 ++ .../common/etree/SynchronizedIntervalMap.java | 7 + .../db/whois/common/iptree/CachedIpTree.java | 5 + .../ripe/db/whois/common/iptree/IpTree.java | 2 + 12 files changed, 371 insertions(+), 33 deletions(-) create mode 100644 whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 0a0423d0d4..6464f36011 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -1,6 +1,9 @@ package net.ripe.db.whois.api.rdap; +import com.google.common.collect.Lists; import net.ripe.db.whois.api.rdap.domain.RelationType; +import net.ripe.db.whois.common.dao.RpslObjectDao; +import net.ripe.db.whois.common.domain.CIString; import net.ripe.db.whois.common.ip.IpInterval; import net.ripe.db.whois.common.ip.Ipv4Resource; import net.ripe.db.whois.common.ip.Ipv6Resource; @@ -10,11 +13,17 @@ import net.ripe.db.whois.common.iptree.Ipv4Tree; import net.ripe.db.whois.common.iptree.Ipv6DomainTree; import net.ripe.db.whois.common.iptree.Ipv6Tree; +import net.ripe.db.whois.common.rpsl.AttributeType; +import net.ripe.db.whois.common.rpsl.RpslObject; import net.ripe.db.whois.common.rpsl.attrs.Domain; +import net.ripe.db.whois.common.search.ManagedAttributeSearch; +import net.ripe.db.whois.update.domain.UpdateMessages; +import org.eclipse.jetty.http.HttpStatus; import org.springframework.stereotype.Service; import java.util.List; + @Service public class RdapRelationService { @@ -23,14 +32,20 @@ public class RdapRelationService { private final Ipv6Tree ip6Tree; private final Ipv4DomainTree ipv4DomainTree; private final Ipv6DomainTree ipv6DomainTree; + private final RpslObjectDao rpslObjectDao; + private final ManagedAttributeSearch managedAttributeSearch; public RdapRelationService(final RdapQueryHandler rdapQueryHandler, final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, - final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree) { + final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, + final RpslObjectDao rpslObjectDao, + final ManagedAttributeSearch managedAttributeSearch) { this.rdapQueryHandler = rdapQueryHandler; this.ip4Tree = ip4Tree; this.ip6Tree = ip6Tree; this.ipv4DomainTree = ipv4DomainTree; this.ipv6DomainTree = ipv6DomainTree; + this.rpslObjectDao = rpslObjectDao; + this.managedAttributeSearch = managedAttributeSearch; } public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ @@ -53,12 +68,23 @@ private List getRelationPkeys(RelationType relationType, IpTree ipTree, private List getIpEntries(final IpTree ipTree, final RelationType relationType, final IpInterval reverseIp) { return switch (relationType) { case UP -> ipTree.findFirstLessSpecific(reverseIp); - case TOP -> List.of((IpEntry) ipTree.findAllLessSpecific(reverseIp).getFirst()); + case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp); case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); - case BOTTOM -> ipTree.findAllMoreSpecific(reverseIp); //TODO: [MH] get the MOST specific, can be more than 1 + case BOTTOM -> ipTree.findMostSpecific(reverseIp); }; } + private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp) { + for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { + final IpEntry ipEntry = (IpEntry) parentEntry; + final RpslObject rpslObject = rpslObjectDao.getById(ipEntry.getObjectId()); + if (managedAttributeSearch.isCoMaintained(rpslObject)){ + return List.of(ipEntry); + } + } + throw new RdapException("404 Not Found", "No top level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + } + private IpTree getIpTree(final IpInterval reverseIp) { if (reverseIp instanceof Ipv4Resource) { return ip4Tree; diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java new file mode 100644 index 0000000000..43caa7f097 --- /dev/null +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java @@ -0,0 +1,35 @@ +package net.ripe.db.whois.api.rdap; + +import jakarta.ws.rs.ext.ParamConverter; +import jakarta.ws.rs.ext.ParamConverterProvider; +import jakarta.ws.rs.ext.Provider; +import net.ripe.db.whois.api.rdap.domain.RdapRequestType; +import net.ripe.db.whois.api.rdap.domain.RelationType; +import org.springframework.stereotype.Component; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +@Component +@Provider +public class RdapRelationTypeConverter implements ParamConverterProvider { + + @Override + public ParamConverter getConverter(Class rawType, Type type, Annotation[] annotations) { + if(!rawType.equals(RelationType.class)) { + return null; + } + + return (ParamConverter) new ParamConverter() { + @Override + public RelationType fromString(String relationType){ + return RelationType.valueOf(relationType.toUpperCase()); + } + + @Override + public String toString(RelationType relationType){ + return relationType.toString(); + } + }; + } +} diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index a4226c8502..4a3b9dc941 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -263,36 +263,14 @@ public Response relationSearch( throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); } - final Set whoisObjectTypes = requestType.getWhoisObjectTypes(key); + final List rpslObjects = handleRelationQuery(request, requestType, relationType, key); - switch (requestType) { - case AUTNUM -> { - String autnumKey = String.format("AS%s", key); - rdapRequestValidator.validateAutnum(autnumKey); - return lookupForAutNum(request, autnumKey); - } - case DOMAIN -> { - rdapRequestValidator.validateDomain(key); - final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); - - final List rpslObjects = relatedPkeys - .stream() - .flatMap(relatedPkey -> rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(DOMAIN), key), request)) - .toList(); - - return Response.ok(rdapObjectMapper.mapSearch( - getRequestUrl(request), - rpslObjects, - maxResultSize)) - .header(CONTENT_TYPE, CONTENT_TYPE_RDAP_JSON) - .build(); - } - case IP -> { - rdapRequestValidator.validateIp(request.getRequestURI(), key); - return lookupWithRedirectUrl(request, whoisObjectTypes, key); - } - default -> throw new RdapException("400 Bad Request", "Invalid or unknown type" + requestType, HttpStatus.BAD_REQUEST_400); - } + return Response.ok(rdapObjectMapper.mapSearch( + getRequestUrl(request), + rpslObjects, + maxResultSize)) + .header(CONTENT_TYPE, CONTENT_TYPE_RDAP_JSON) + .build(); } private Response lookupWithRedirectUrl(final HttpServletRequest request, final Set objectTypes, final String key) { @@ -537,6 +515,34 @@ private String objectTypesToString(final Collection objectTypes) { return COMMA_JOINER.join(objectTypes.stream().map(ObjectType::getName).toList()); } + private List handleRelationQuery(final HttpServletRequest request, final RdapRequestType requestType, + final RelationType relationType, final String key) { + final List rpslObjects; + switch (requestType) { + case AUTNUM -> throw new RdapException("400 Bad Request", "Relation queries not allowed for autnum", HttpStatus.BAD_REQUEST_400); + case DOMAIN -> { + rdapRequestValidator.validateDomain(key); + final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); + + rpslObjects = relatedPkeys + .stream() + .flatMap(relatedPkey -> rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(DOMAIN), key), request)) + .toList(); + } + case IPS -> { + rdapRequestValidator.validateIp(request.getRequestURI(), key); + final List relatedPkeys = rdapRelationService.getInetnumRelationPkeys(key, relationType); + + rpslObjects = relatedPkeys + .stream() + .flatMap(relatedPkey -> rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(INETNUM, INET6NUM), relatedPkey), request)) + .toList(); + } + default -> throw new RdapException("400 Bad Request", "Invalid or unknown type " + requestType.toString().toLowerCase(), HttpStatus.BAD_REQUEST_400); + } + return rpslObjects; + } + private Response handleSearch(final String[] fields, final String term, final HttpServletRequest request) { LOGGER.debug("Search {} for {}", fields, term); diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java index 5d47b9185c..5922373bad 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java @@ -20,12 +20,17 @@ public class RdapServletDeployer implements ServletDeployer { private final RdapService rdapService; private final RdapExceptionMapper rdapExceptionMapper; private final RdapRequestTypeConverter rdapRequestTypeConverter; + private final RdapRelationTypeConverter rdapRelationTypeConverter; @Autowired - public RdapServletDeployer(final RdapService rdapService, final RdapExceptionMapper rdapExceptionMapper, final RdapRequestTypeConverter rdapRequestTypeConverter) { + public RdapServletDeployer(final RdapService rdapService, + final RdapExceptionMapper rdapExceptionMapper, + final RdapRequestTypeConverter rdapRequestTypeConverter, + final RdapRelationTypeConverter rdapRelationTypeConverter) { this.rdapService = rdapService; this.rdapExceptionMapper = rdapExceptionMapper; this.rdapRequestTypeConverter = rdapRequestTypeConverter; + this.rdapRelationTypeConverter = rdapRelationTypeConverter; } @Override @@ -43,6 +48,7 @@ public void deploy(final WebAppContext context) { resourceConfig.register(rdapService); resourceConfig.register(rdapRequestTypeConverter); resourceConfig.register(rdapExceptionMapper); + resourceConfig.register(rdapRelationTypeConverter); resourceConfig.register(rdapJsonProvider); context.addServlet(new ServletHolder("Whois RDAP REST API", new ServletContainer(resourceConfig)), "/rdap/*"); } diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java index 36e54bcb0f..97b27361ba 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java @@ -23,18 +23,36 @@ public Set getWhoisObjectTypes(final String key) { } }, + AUTNUMS { + public Set getWhoisObjectTypes(final String key) { + return ImmutableSet.of(AUT_NUM, AS_BLOCK); + } + }, + DOMAIN { public Set getWhoisObjectTypes(final String key) { return ImmutableSet.of(ObjectType.DOMAIN); } }, + DOMAINS { + public Set getWhoisObjectTypes(final String key) { + return ImmutableSet.of(ObjectType.DOMAIN); + } + }, + IP { public Set getWhoisObjectTypes(final String key) { return key.contains(":") ? ImmutableSet.of(INET6NUM) : ImmutableSet.of(INETNUM); } }, + IPS { + public Set getWhoisObjectTypes(final String key) { + return key.contains(":") ? ImmutableSet.of(INET6NUM) : ImmutableSet.of(INETNUM); + } + }, + ENTITY { public Set getWhoisObjectTypes(final String key) { return ImmutableSet.of(PERSON, ROLE, MNTNER); diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index f80966fd39..74bc9b1430 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -24,6 +24,7 @@ import net.ripe.db.whois.api.rdap.domain.Redaction; import net.ripe.db.whois.api.rdap.domain.Remark; import net.ripe.db.whois.api.rdap.domain.Role; +import net.ripe.db.whois.api.rdap.domain.SearchResult; import net.ripe.db.whois.common.rpsl.AttributeType; import net.ripe.db.whois.common.rpsl.RpslObject; import net.ripe.db.whois.query.support.TestWhoisLog; @@ -3127,6 +3128,80 @@ public void get_help_response() { assertCopyrightLink(help.getLinks(), "https://rdap.db.ripe.net/help"); } + /*RIR Search*/ + + @Test + public void get_bottom_then_most_specific(){ + loadRelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/bottom/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(3)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.0")); + assertThat(ipResults.get(1).getHandle(), is("192.0.2.128 - 192.0.2.191")); + assertThat(ipResults.get(2).getHandle(), is("192.0.2.192 - 192.0.2.255")); + } + + @Test + public void get_up_then_parent(){ + loadRelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); + } + + @Test + public void get_top_then_less_specific_allocated_assigned_then_first_parent(){ + loadRelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.255")); + } + + @Test + public void get_non_existing_top_then_less_specific_allocated_assigned_then_404(){ + loadRelationTreeExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/top/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); + } + + @Test + public void get_most_specific_wrong_type_then_400(){ + + final BadRequestException badRequestException = assertThrows(BadRequestException.class, () -> { + createResource("ip/rirSearch1/bottom/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + assertErrorTitle(badRequestException, "400 Bad Request"); + assertErrorStatus(badRequestException, HttpStatus.BAD_REQUEST_400); + assertErrorDescription(badRequestException, "Invalid or unknown type ip"); + } + + + /* Helper methods*/ + private void assertCommon(RdapObject object) { assertThat(object.getPort43(), is("whois.ripe.net")); assertThat(object.getRdapConformance(), hasSize(4)); @@ -3209,4 +3284,121 @@ private void createEntityRedactionObjects() { "source: TEST"); } + private void loadRelationTreeExample(){ + /* + +--------------+ + | 192.0.2.0/24 | + +--------------+ + / \ + +--------------+ +----------------+ + | 192.0.2.0/25 | | 192.0.2.128/25 | + +--------------+ +----------------+ + / / \ + +--------------+ +----------------+ +----------------+ + | 192.0.2.0/28 | | 192.0.2.128/26 | | 192.0.2.192/26 | + +--------------+ +----------------+ +----------------+ + / + +--------------+ + | 192.0.2.0/32 | + +--------------+ + */ + + databaseHelper.addObject("" + + "inetnum: 192.0.2.0 - 192.0.2.255\n" + // /24 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: ALLOCATED PA\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + // One branch + databaseHelper.addObject("" + + "inetnum: 192.0.2.0 - 192.0.2.127\n" + // /25 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: OTHER\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + databaseHelper.addObject("" + + "inetnum: 192.0.2.0 - 192.0.2.15\n" + // /28 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: OTHER\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inetnum: 192.0.2.0 - 192.0.2.0\n" + // /32 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: OTHER\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + //Another branch + + databaseHelper.addObject("" + + "inetnum: 192.0.2.128 - 192.0.2.255\n" + // /25 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: OTHER\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inetnum: 192.0.2.128 - 192.0.2.191\n" + // /26 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: OTHER\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inetnum: 192.0.2.192 - 192.0.2.255\n" + // /26 + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: OTHER\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + ipTreeUpdater.rebuild(); + } } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java index 66d937eadf..9d31b78ac7 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java @@ -161,4 +161,20 @@ public interface IntervalMap, V> { * intervals. */ List findExactAndAllMoreSpecific(K key); + + /** + * Finds all values associated with intervals that contained within (more specific than) + * and whose prefix is higher, more specific + * key. + *

+ *

+ * The resulting values the most specific values associated with the + * key. + * + * @param key the key to find the exact and all levels more specific values + * for + * @return the (possibly empty) list of values associated with the matching + * intervals. + */ + List findMostSpecific(K key); } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/MultiValueIntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/MultiValueIntervalMap.java index 994bf327fe..e86a9f5883 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/MultiValueIntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/MultiValueIntervalMap.java @@ -103,4 +103,9 @@ public List findAllMoreSpecific(K key) { public List findExactAndAllMoreSpecific(K key) { return unroll(wrapped.findExactAndAllMoreSpecific(key)); } + + @Override + public List findMostSpecific(K key) { + return unroll(wrapped.findMostSpecific(key)); + } } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java index 53c6ee8897..4cc63e680a 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java @@ -2,7 +2,10 @@ import com.google.common.collect.Lists; import net.ripe.db.whois.common.collect.CollectionHelper; +import net.ripe.db.whois.common.domain.IpRanges; import net.ripe.db.whois.common.ip.Interval; +import net.ripe.db.whois.common.ip.IpInterval; +import net.ripe.db.whois.common.rpsl.AttributeParser; import org.apache.commons.lang.Validate; import java.util.ArrayList; @@ -119,6 +122,12 @@ public List findExactAndAllMoreSpecific(K key) { return mapToValues(internalFindExactAndAllMoreSpecific(key)); } + @Override + public List findMostSpecific(K key) { + Validate.notNull(key); + return mapToValues(internalFindMostSpecific(key)); + } + /** * Clears all values from the map. */ @@ -226,6 +235,17 @@ private List> internalFindFirstMoreSpecific(K range) { return result; } + private List> internalFindMostSpecific(K range){ + final List> result = internalFindAllMoreSpecific(range); + if (result.isEmpty()){ + return Lists.newArrayList(); + } + + return result.parallelStream() + .filter( kvInternalNode -> kvInternalNode.getChildren().isEmpty()) + .toList(); + } + private List> internalFindAllMoreSpecific(K range) { List> result = internalFindExactAndAllMoreSpecific(range); if (!result.isEmpty() && result.get(0).getInterval().equals(range)) { diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/SynchronizedIntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/SynchronizedIntervalMap.java index f1d99e0066..07949e0e74 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/SynchronizedIntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/SynchronizedIntervalMap.java @@ -104,6 +104,13 @@ public List findExactAndAllMoreSpecific(K key) { } } + @Override + public List findMostSpecific(K key) { + synchronized (mutex) { + return wrapped.findMostSpecific(key); + } + } + @Override public void clear() { synchronized (mutex) { diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/CachedIpTree.java b/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/CachedIpTree.java index 44e4991c23..69d74c3263 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/CachedIpTree.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/CachedIpTree.java @@ -59,4 +59,9 @@ public List findFirstMoreSpecific(final K key) { public List findAllMoreSpecific(final K key) { return getIntervalMap().findAllMoreSpecific(key); } + + @Override + public List findMostSpecific(final K key){ + return getIntervalMap().findMostSpecific(key); + } } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/IpTree.java b/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/IpTree.java index 9b82ca6ad2..a53acabb57 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/IpTree.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/iptree/IpTree.java @@ -19,4 +19,6 @@ public interface IpTree, V extends NestedIntervalMap.Key List findFirstMoreSpecific(K key); List findAllMoreSpecific(K key); + + List findMostSpecific(K key); } From 387c40b6aafd4baf32108942e8cd75ac2e92adac Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 3 Dec 2024 16:17:54 +0100 Subject: [PATCH 03/34] feat: fix bottom to match the RFC --- .../whois/api/rdap/RdapRelationService.java | 91 +++++++++++++-- .../api/rdap/RdapServiceTestIntegration.java | 107 +++++++++++++----- .../common/rpsl/attrs/Inet6numStatus.java | 5 + .../whois/common/rpsl/attrs/InetStatus.java | 2 + .../common/rpsl/attrs/InetnumStatus.java | 5 + 5 files changed, 176 insertions(+), 34 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 6464f36011..0f06de0d4d 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -1,6 +1,8 @@ package net.ripe.db.whois.api.rdap; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import net.ripe.db.whois.api.rdap.domain.Ip; import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.common.dao.RpslObjectDao; import net.ripe.db.whois.common.domain.CIString; @@ -14,14 +16,18 @@ import net.ripe.db.whois.common.iptree.Ipv6DomainTree; import net.ripe.db.whois.common.iptree.Ipv6Tree; import net.ripe.db.whois.common.rpsl.AttributeType; +import net.ripe.db.whois.common.rpsl.ObjectType; import net.ripe.db.whois.common.rpsl.RpslObject; import net.ripe.db.whois.common.rpsl.attrs.Domain; +import net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus; +import net.ripe.db.whois.common.rpsl.attrs.InetnumStatus; import net.ripe.db.whois.common.search.ManagedAttributeSearch; -import net.ripe.db.whois.update.domain.UpdateMessages; import org.eclipse.jetty.http.HttpStatus; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Objects; +import java.util.Set; @Service @@ -33,19 +39,16 @@ public class RdapRelationService { private final Ipv4DomainTree ipv4DomainTree; private final Ipv6DomainTree ipv6DomainTree; private final RpslObjectDao rpslObjectDao; - private final ManagedAttributeSearch managedAttributeSearch; public RdapRelationService(final RdapQueryHandler rdapQueryHandler, final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, - final RpslObjectDao rpslObjectDao, - final ManagedAttributeSearch managedAttributeSearch) { + final RpslObjectDao rpslObjectDao) { this.rdapQueryHandler = rdapQueryHandler; this.ip4Tree = ip4Tree; this.ip6Tree = ip6Tree; this.ipv4DomainTree = ipv4DomainTree; this.ipv6DomainTree = ipv6DomainTree; this.rpslObjectDao = rpslObjectDao; - this.managedAttributeSearch = managedAttributeSearch; } public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ @@ -67,24 +70,96 @@ private List getRelationPkeys(RelationType relationType, IpTree ipTree, private List getIpEntries(final IpTree ipTree, final RelationType relationType, final IpInterval reverseIp) { return switch (relationType) { - case UP -> ipTree.findFirstLessSpecific(reverseIp); + case UP -> List.of(searchFirstLessSpecificCoMntner(ipTree, reverseIp)); case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp); case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); - case BOTTOM -> ipTree.findMostSpecific(reverseIp); + case BOTTOM -> searchMostSpecificFillingOverlaps(ipTree, reverseIp); }; } + private List searchMostSpecificFillingOverlaps(final IpTree ipTree, final IpInterval reverseIp){ + final List mostSpecificValues = ipTree.findMostSpecific(reverseIp); + final List> ipResources = mostSpecificValues.stream().map(ip -> IpInterval.parse(ip.getKey().toString())).toList(); + final Set mostSpecificFillingOverlaps = Sets.newConcurrentHashSet(); + + for (int countIps = 0; countIps < mostSpecificValues.size(); countIps++){ + final IpInterval firstResource = ipResources.get(countIps); + processChildren(ipTree, reverseIp, firstResource, mostSpecificFillingOverlaps); + } + return mostSpecificFillingOverlaps.stream().toList(); + } + + private static void processChildren(final IpTree ipTree, final IpInterval reverseIp, + final IpInterval mostSpecificResource, + final Set mostSpecificFillingOverlaps) { + + final List siblingsAndExact = findSiblingsAndExact(ipTree, mostSpecificResource); + + final IpEntry firstResourceIpEntry = siblingsAndExact.stream() + .filter(sibling -> sibling.getKey().toString().equals(mostSpecificResource.toString())) + .findFirst().orElse(null); + + mostSpecificFillingOverlaps.add(firstResourceIpEntry); + + final IpInterval firstSibling = (IpInterval)siblingsAndExact.getFirst().getKey(); + final IpInterval lastSibling = (IpInterval)siblingsAndExact.getLast().getKey(); + + final IpEntry parent = (IpEntry) ipTree.findFirstLessSpecific(IpInterval.parse(mostSpecificResource.toString())).getFirst(); + final IpInterval parentInterval = (IpInterval) parent.getKey(); + + if (!parentInterval.equals(reverseIp) && + !childrenCoverParentRange(firstSibling, lastSibling, parentInterval)){ + processChildren(ipTree, reverseIp, parentInterval, mostSpecificFillingOverlaps); + } + } + + private static boolean childrenCoverParentRange(final IpInterval firstResource, final IpInterval lastResource, final IpInterval parent){ + if (firstResource instanceof Ipv4Resource ipv4Resource){ + final Ipv4Resource lastIpv4Resource = (Ipv4Resource)lastResource; + final Ipv4Resource parentIpv4Resource = (Ipv4Resource)parent; + return ipv4Resource.begin() == parentIpv4Resource.begin() && lastIpv4Resource.end() == parentIpv4Resource.end(); + } + + final Ipv6Resource ipv6Resource = (Ipv6Resource)firstResource; + final Ipv6Resource lastIpv6Resource = (Ipv6Resource)lastResource; + final Ipv6Resource parentIpv6Resource = (Ipv6Resource)parent; + return Objects.equals(ipv6Resource.begin(), parentIpv6Resource.begin()) && Objects.equals(lastIpv6Resource.end(), parentIpv6Resource.end()); + } + + private static List findSiblingsAndExact(final IpTree ipTree, final IpInterval parentResource) { + final IpEntry parent = (IpEntry)ipTree.findFirstLessSpecific(parentResource).getFirst(); + return ipTree.findFirstMoreSpecific(IpInterval.parse(parent.getKey().toString())); + } + + private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp){ + final IpEntry firstLessSpecific = (IpEntry) ipTree.findFirstLessSpecific(reverseIp).getFirst(); + final RpslObject rpslObject = rpslObjectDao.getById(firstLessSpecific.getObjectId()); + + if (!isOutOfRegionOrRoot(rpslObject)) { + return firstLessSpecific; + } + + throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + } + + private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp) { for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { final IpEntry ipEntry = (IpEntry) parentEntry; final RpslObject rpslObject = rpslObjectDao.getById(ipEntry.getObjectId()); - if (managedAttributeSearch.isCoMaintained(rpslObject)){ + if (!isOutOfRegionOrRoot(rpslObject)){ return List.of(ipEntry); } } throw new RdapException("404 Not Found", "No top level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } + private static boolean isOutOfRegionOrRoot(final RpslObject rpslObject) { + final CIString status = rpslObject.getValueForAttribute(AttributeType.STATUS); + return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(status).isOutOfRegionOrRoot()) + || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(status).isOutOfRegionOrRoot()); + } + private IpTree getIpTree(final IpInterval reverseIp) { if (reverseIp instanceof Ipv4Resource) { return ip4Tree; diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 74bc9b1430..a1b7e36ab5 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -173,7 +173,7 @@ public void setup() { "country: NL\n" + "tech-c: TP1-TEST\n" + "admin-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED UNSPECIFIED\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3130,37 +3130,38 @@ public void get_help_response() { /*RIR Search*/ + @Test - public void get_bottom_then_most_specific(){ - loadRelationTreeExample(); + public void get_up_then_parent(){ + loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/bottom/192.0.2.0/24") + final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(3)); - assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.0")); - assertThat(ipResults.get(1).getHandle(), is("192.0.2.128 - 192.0.2.191")); - assertThat(ipResults.get(2).getHandle(), is("192.0.2.192 - 192.0.2.255")); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); // /26 } @Test - public void get_up_then_parent(){ - loadRelationTreeExample(); + public void get_non_existing_up_then_404(){ + loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); - final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/24"); } @Test public void get_top_then_less_specific_allocated_assigned_then_first_parent(){ - loadRelationTreeExample(); + loadIpv4RelationTreeExample(); final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28") .request(MediaType.APPLICATION_JSON_TYPE) @@ -3168,12 +3169,12 @@ public void get_top_then_less_specific_allocated_assigned_then_first_parent(){ final List ipResults = searchResult.getIpSearchResults(); assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.255")); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.255")); // /24 } @Test public void get_non_existing_top_then_less_specific_allocated_assigned_then_404(){ - loadRelationTreeExample(); + loadIpv4RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { createResource("ips/rirSearch1/top/192.0.2.0/24") @@ -3186,6 +3187,35 @@ public void get_non_existing_top_then_less_specific_allocated_assigned_then_404( assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); } + @Test + public void get_bottom_then_most_specific(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/bottom/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(5)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.0")); //32 + assertThat(ipResults.get(1).getHandle(), is("192.0.2.0 - 192.0.2.15")); //28 + assertThat(ipResults.get(2).getHandle(), is("192.0.2.0 - 192.0.2.127")); //25 + assertThat(ipResults.get(3).getHandle(), is("192.0.2.128 - 192.0.2.191")); //26 + assertThat(ipResults.get(4).getHandle(), is("192.0.2.192 - 192.0.2.255")); //26 + } + + @Test + public void get_no_more_specific_bottom_then_empty_response(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/bottom/192.0.2.0/32") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults, is(nullValue())); + } + @Test public void get_most_specific_wrong_type_then_400(){ @@ -3199,6 +3229,31 @@ public void get_most_specific_wrong_type_then_400(){ assertErrorDescription(badRequestException, "Invalid or unknown type ip"); } + @Test + public void get_down_then_immediate_child(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/down/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(2)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); + assertThat(ipResults.get(1).getHandle(), is("192.0.2.128 - 192.0.2.255")); + } + + @Test + public void get_down_when_no_child_then_empty_response(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/down/192.0.2.0/32") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults, is(nullValue())); + } /* Helper methods*/ @@ -3284,7 +3339,7 @@ private void createEntityRedactionObjects() { "source: TEST"); } - private void loadRelationTreeExample(){ + private void loadIpv4RelationTreeExample(){ /* +--------------+ | 192.0.2.0/24 | @@ -3325,7 +3380,7 @@ private void loadRelationTreeExample(){ "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3339,7 +3394,7 @@ private void loadRelationTreeExample(){ "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3352,7 +3407,7 @@ private void loadRelationTreeExample(){ "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ASSIGNED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3367,7 +3422,7 @@ private void loadRelationTreeExample(){ "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3380,7 +3435,7 @@ private void loadRelationTreeExample(){ "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3393,7 +3448,7 @@ private void loadRelationTreeExample(){ "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java index d3422508fa..2e9975560e 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java @@ -80,6 +80,11 @@ public boolean worksWithParentInHierarchy(final InetStatus parentInHierarchyMain return true; } + @Override + public boolean isOutOfRegionOrRoot() { + return this.equals(ALLOCATED_BY_RIR); + } + @Override public boolean needsOrgReference() { return NEEDS_ORG_REFERENCE.contains(this); diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java index 3e39023beb..60a3dbb134 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java @@ -16,4 +16,6 @@ public interface InetStatus { boolean worksWithParentStatus(InetStatus parent, boolean objectHasRsMaintainer); boolean worksWithParentInHierarchy(InetStatus parentInHierarchyMaintainedByRs, boolean parentHasRsMntLower); + + boolean isOutOfRegionOrRoot(); } diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java index 811480c301..243c6a64ad 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java @@ -107,6 +107,11 @@ public boolean worksWithParentInHierarchy(final InetStatus parentInHierarchyMain return true; } + @Override + public boolean isOutOfRegionOrRoot() { + return this.equals(ALLOCATED_UNSPECIFIED); + } + @Override public boolean needsOrgReference() { return NEEDS_ORG_REFERENCE.contains(this); From 4cbd0be3d06e1477e604d8687beb193321723dc8 Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 3 Dec 2024 16:26:03 +0100 Subject: [PATCH 04/34] feat: remove unecessary changes and fix typo --- .../java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java | 3 +-- .../main/java/net/ripe/db/whois/common/etree/IntervalMap.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java index 840761d4c9..1743319603 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapObjectMapper.java @@ -284,8 +284,7 @@ private RpslObject getRpslObject(final int objectId) { } } - private RdapObject getRdapObject(final String requestUrl, final RpslObject rpslObject, - @Nullable final AbuseContact abuseContact) { + private RdapObject getRdapObject(final String requestUrl, final RpslObject rpslObject, @Nullable final AbuseContact abuseContact) { RdapObject rdapResponse; final ObjectType rpslObjectType = rpslObject.getType(); diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java index 9d31b78ac7..10062cef6e 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java @@ -163,12 +163,12 @@ public interface IntervalMap, V> { List findExactAndAllMoreSpecific(K key); /** - * Finds all values associated with intervals that contained within (more specific than) + * Finds all values associated with intervals that are contained within (more specific than) * and whose prefix is higher, more specific * key. *

*

- * The resulting values the most specific values associated with the + * The resulting values are the most specific values associated with the * key. * * @param key the key to find the exact and all levels more specific values From 32ca759206f222701ebb4f3ac3695d972027e0aa Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 4 Dec 2024 15:53:37 +0100 Subject: [PATCH 05/34] feat: add more ITs --- .../whois/api/rdap/RdapRelationService.java | 62 ++- .../ripe/db/whois/api/rdap/RdapService.java | 6 +- .../api/rdap/RdapServiceTestIntegration.java | 471 +++++++++++++++++- 3 files changed, 501 insertions(+), 38 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 0f06de0d4d..c3db254253 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -1,8 +1,6 @@ package net.ripe.db.whois.api.rdap; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import net.ripe.db.whois.api.rdap.domain.Ip; import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.common.dao.RpslObjectDao; import net.ripe.db.whois.common.domain.CIString; @@ -21,10 +19,12 @@ import net.ripe.db.whois.common.rpsl.attrs.Domain; import net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus; import net.ripe.db.whois.common.rpsl.attrs.InetnumStatus; -import net.ripe.db.whois.common.search.ManagedAttributeSearch; import org.eclipse.jetty.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import javax.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.Set; @@ -33,17 +33,17 @@ @Service public class RdapRelationService { - private final RdapQueryHandler rdapQueryHandler; + private static final Logger LOGGER = LoggerFactory.getLogger(RdapRelationService.class); + private final Ipv4Tree ip4Tree; private final Ipv6Tree ip6Tree; private final Ipv4DomainTree ipv4DomainTree; private final Ipv6DomainTree ipv6DomainTree; private final RpslObjectDao rpslObjectDao; - public RdapRelationService(final RdapQueryHandler rdapQueryHandler, final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, + public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, final RpslObjectDao rpslObjectDao) { - this.rdapQueryHandler = rdapQueryHandler; this.ip4Tree = ip4Tree; this.ip6Tree = ip6Tree; this.ipv4DomainTree = ipv4DomainTree; @@ -54,17 +54,17 @@ public RdapRelationService(final RdapQueryHandler rdapQueryHandler, final Ipv4Tr public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ final Domain domain = Domain.parse(pkey); final IpInterval reverseIp = domain.getReverseIp(); + final List ipEntries = getIpEntries(getIpDomainTree(reverseIp), relationType, reverseIp); - return getRelationPkeys(relationType, getIpDomainTree(reverseIp), reverseIp); + return ipEntries + .stream() + .map(ipEntry -> rpslObjectDao.getById(ipEntry.getObjectId()).getKey().toString()) + .toList(); } public List getInetnumRelationPkeys(final String pkey, final RelationType relationType){ final IpInterval ip = IpInterval.parse(pkey); - return getRelationPkeys(relationType, getIpTree(ip), ip); - } - - private List getRelationPkeys(RelationType relationType, IpTree ipTree, IpInterval ip) { - final List ipEntries = getIpEntries(ipTree, relationType, ip); + final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip); return ipEntries.stream().map(ipEntry -> ipEntry.getKey().toString()).toList(); } @@ -127,39 +127,51 @@ private static boolean childrenCoverParentRange(final IpInterval firstResource, } private static List findSiblingsAndExact(final IpTree ipTree, final IpInterval parentResource) { - final IpEntry parent = (IpEntry)ipTree.findFirstLessSpecific(parentResource).getFirst(); - return ipTree.findFirstMoreSpecific(IpInterval.parse(parent.getKey().toString())); + final List parentList = ipTree.findFirstLessSpecific(parentResource); + if (parentList.isEmpty()){ + return ipTree.findExact(parentResource); + } + return ipTree.findFirstMoreSpecific(IpInterval.parse(parentList.getFirst().getKey().toString())); } private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp){ - final IpEntry firstLessSpecific = (IpEntry) ipTree.findFirstLessSpecific(reverseIp).getFirst(); - final RpslObject rpslObject = rpslObjectDao.getById(firstLessSpecific.getObjectId()); - - if (!isOutOfRegionOrRoot(rpslObject)) { - return firstLessSpecific; + final List parentList = ipTree.findFirstLessSpecific(reverseIp); + if (parentList.isEmpty() || isOutOfRegionOrRoot(parentList.getFirst())){ + throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } - - throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + return parentList.getFirst(); } - private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp) { for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { final IpEntry ipEntry = (IpEntry) parentEntry; - final RpslObject rpslObject = rpslObjectDao.getById(ipEntry.getObjectId()); - if (!isOutOfRegionOrRoot(rpslObject)){ + if (!isOutOfRegionOrRoot(ipEntry)){ return List.of(ipEntry); } } throw new RdapException("404 Not Found", "No top level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } - private static boolean isOutOfRegionOrRoot(final RpslObject rpslObject) { + private boolean isOutOfRegionOrRoot(final IpEntry firstLessSpecific) { + final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); + if (rpslObject == null) { + return true; + } final CIString status = rpslObject.getValueForAttribute(AttributeType.STATUS); return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(status).isOutOfRegionOrRoot()) || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(status).isOutOfRegionOrRoot()); } + @Nullable + private RpslObject getResourceByKey(final String key){ + final RpslObject rpslObject = rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); + if (rpslObject == null){ + LOGGER.error("INET(6)NUM {} does not exist in RIPE Database ", key); + return rpslObjectDao.getByKeyOrNull(ObjectType.INETNUM, key); + } + return rpslObject; + } + private IpTree getIpTree(final IpInterval reverseIp) { if (reverseIp instanceof Ipv4Resource) { return ip4Tree; diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 4a3b9dc941..14f378314a 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -519,14 +519,14 @@ private List handleRelationQuery(final HttpServletRequest request, f final RelationType relationType, final String key) { final List rpslObjects; switch (requestType) { - case AUTNUM -> throw new RdapException("400 Bad Request", "Relation queries not allowed for autnum", HttpStatus.BAD_REQUEST_400); - case DOMAIN -> { + case AUTNUMS -> throw new RdapException("400 Bad Request", "Relation queries not allowed for autnum", HttpStatus.BAD_REQUEST_400); + case DOMAINS -> { rdapRequestValidator.validateDomain(key); final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); rpslObjects = relatedPkeys .stream() - .flatMap(relatedPkey -> rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(DOMAIN), key), request)) + .flatMap(relatedPkey -> rdapQueryHandler.handleQueryStream(getQueryObject(ImmutableSet.of(DOMAIN), relatedPkey), request)) .toList(); } case IPS -> { diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index a1b7e36ab5..cf0c2bb060 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -185,7 +185,7 @@ public void setup() { "country: NL\n" + "tech-c: TP1-TEST\n" + "admin-c: TP1-TEST\n" + - "status: OTHER\n" + + "status: ALLOCATED-BY-RIR\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + @@ -3130,6 +3130,18 @@ public void get_help_response() { /*RIR Search*/ + @Test + public void get_up_autnum_then_400(){ + final BadRequestException badRequestException = assertThrows(BadRequestException.class, () -> { + createResource("autnums/rirSearch1/up/AS123") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(badRequestException, "400 Bad Request"); + assertErrorStatus(badRequestException, HttpStatus.BAD_REQUEST_400); + assertErrorDescription(badRequestException, "Relation queries not allowed for autnum"); + } @Test public void get_up_then_parent(){ @@ -3144,6 +3156,49 @@ public void get_up_then_parent(){ assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); // /26 } + @Test + public void get_ipv6_up_then_parent(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/up/2001:db8::/32") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("2001::/16")); + } + + @Test + public void get_domain_up_then_parent(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final SearchResult searchResult = createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List domainResults = searchResult.getDomainSearchResults(); + assertThat(domainResults.size(), is(1)); + assertThat(domainResults.getFirst().getHandle(), is("2.0.192.in-addr.arpa")); + } + + @Test + public void get_non_existing_domain_up_then_404(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("domains/rirSearch1/up/0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.0.0/16"); + } + @Test public void get_non_existing_up_then_404(){ loadIpv4RelationTreeExample(); @@ -3160,7 +3215,7 @@ public void get_non_existing_up_then_404(){ } @Test - public void get_top_then_less_specific_allocated_assigned_then_first_parent(){ + public void get_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv4RelationTreeExample(); final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28") @@ -3173,7 +3228,50 @@ public void get_top_then_less_specific_allocated_assigned_then_first_parent(){ } @Test - public void get_non_existing_top_then_less_specific_allocated_assigned_then_404(){ + public void get_ipv6_top_then_less_specific_allocated_assigned_first_parent(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/top/2001:db8::/32") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("2000::/3")); + } + + @Test + public void get_domain_top_then_less_specific_allocated_assigned_first_parent(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final SearchResult searchResult = createResource("domains/rirSearch1/top/1.2.0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List domainResults = searchResult.getDomainSearchResults(); + assertThat(domainResults.size(), is(1)); + assertThat(domainResults.getFirst().getHandle(), is("0.192.in-addr.arpa")); + } + + @Test + public void get_non_existing_domain_top_then_404(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("domains/rirSearch1/top/0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No top level object has been found for 192.0.0.0/16"); + } + + @Test + public void get_non_existing_top_then_404(){ loadIpv4RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { @@ -3188,7 +3286,7 @@ public void get_non_existing_top_then_less_specific_allocated_assigned_then_404( } @Test - public void get_bottom_then_most_specific(){ + public void get_bottom_then_bottom(){ loadIpv4RelationTreeExample(); final SearchResult searchResult = createResource("ips/rirSearch1/bottom/192.0.2.0/24") @@ -3205,7 +3303,88 @@ public void get_bottom_then_most_specific(){ } @Test - public void get_no_more_specific_bottom_then_empty_response(){ + public void get_bottom_ipv6_then_bottom(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/bottom/2000::/3") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(6)); + assertThat(ipResults.getFirst().getHandle(), is("2001::/16")); + assertThat(ipResults.get(1).getHandle(), is("2001::/23")); + assertThat(ipResults.get(2).getHandle(), is("2001:db8::/32")); + assertThat(ipResults.get(3).getHandle(), is("2400::/12")); + assertThat(ipResults.get(4).getHandle(), is("2600::/12")); + assertThat(ipResults.get(5).getHandle(), is("2800::/12")); + } + + @Test + public void get_bottom_ipv6_cover_parent_then_bottom(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/bottom/FC00::/7") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(2)); + assertThat(ipResults.getFirst().getHandle(), is("FC00::/8")); + assertThat(ipResults.get(1).getHandle(), is("FD00::/8")); + } + + @Test + public void get_bottom_from_root_parent_then_bottom(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/bottom/::/0") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(9)); + assertThat(ipResults.getFirst().getHandle(), is("2001::/16")); + assertThat(ipResults.get(1).getHandle(), is("2001::/23")); + assertThat(ipResults.get(2).getHandle(), is("2000::/3")); + assertThat(ipResults.get(3).getHandle(), is("2001:db8::/32")); + assertThat(ipResults.get(4).getHandle(), is("2400::/12")); + assertThat(ipResults.get(5).getHandle(), is("2600::/12")); + assertThat(ipResults.get(6).getHandle(), is("2800::/12")); + assertThat(ipResults.get(7).getHandle(), is("FC00::/8")); + assertThat(ipResults.get(8).getHandle(), is("FD00::/8")); + } + + @Test + public void get_domain_bottom_then_bottom(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final SearchResult searchResult = createResource("domains/rirSearch1/bottom/0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List domainResults = searchResult.getDomainSearchResults(); + assertThat(domainResults.size(), is(2)); + assertThat(domainResults.getFirst().getHandle(), is("1.2.0.192.in-addr.arpa")); + assertThat(domainResults.get(1).getHandle(), is("2.0.192.in-addr.arpa")); + } + + @Test + public void get_non_existing_domain_bottom_then_empty(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final SearchResult searchResult = createResource("domains/rirSearch1/bottom/1.2.0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List domainResults = searchResult.getDomainSearchResults(); + assertThat(domainResults, is(nullValue())); + } + + @Test + public void get_non_existing_bottom_then_empty_response(){ loadIpv4RelationTreeExample(); final SearchResult searchResult = createResource("ips/rirSearch1/bottom/192.0.2.0/32") @@ -3217,7 +3396,7 @@ public void get_no_more_specific_bottom_then_empty_response(){ } @Test - public void get_most_specific_wrong_type_then_400(){ + public void get_bottom_wrong_type_then_400(){ final BadRequestException badRequestException = assertThrows(BadRequestException.class, () -> { createResource("ip/rirSearch1/bottom/192.0.2.0/24") @@ -3243,6 +3422,49 @@ public void get_down_then_immediate_child(){ assertThat(ipResults.get(1).getHandle(), is("192.0.2.128 - 192.0.2.255")); } + @Test + public void get_down_ipv6_then_immediate_child(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/down/2000::/3") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(4)); + assertThat(ipResults.getFirst().getHandle(), is("2001::/16")); + assertThat(ipResults.get(1).getHandle(), is("2400::/12")); + assertThat(ipResults.get(2).getHandle(), is("2600::/12")); + assertThat(ipResults.get(3).getHandle(), is("2800::/12")); + } + + @Test + public void get_down_domain_then_immediate_child(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final SearchResult searchResult = createResource("domains/rirSearch1/down/0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List domainResults = searchResult.getDomainSearchResults(); + assertThat(domainResults.size(), is(1)); + assertThat(domainResults.getFirst().getHandle(), is("2.0.192.in-addr.arpa")); + } + + @Test + public void get_down_domain_no_child_then_empty_response(){ + loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); + + final SearchResult searchResult = createResource("domains/rirSearch1/down/1.2.0.192.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List domainResults = searchResult.getDomainSearchResults(); + assertThat(domainResults, is(nullValue())); + } + @Test public void get_down_when_no_child_then_empty_response(){ loadIpv4RelationTreeExample(); @@ -3388,31 +3610,32 @@ private void loadIpv4RelationTreeExample(){ databaseHelper.addObject("" + - "inetnum: 192.0.2.0 - 192.0.2.15\n" + // /28 + "inetnum: 192.0.2.0 - 192.0.2.0\n" + // /32 "netname: TEST-NET-NAME\n" + "descr: TEST network\n" + "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: ALLOCATED PA\n" + + "status: ASSIGNED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + "source: TEST"); databaseHelper.addObject("" + - "inetnum: 192.0.2.0 - 192.0.2.0\n" + // /32 + "inetnum: 192.0.2.0 - 192.0.2.15\n" + // /28 "netname: TEST-NET-NAME\n" + "descr: TEST network\n" + "country: NL\n" + "language: en\n" + "tech-c: TP1-TEST\n" + - "status: ASSIGNED PA\n" + + "status: ALLOCATED PA\n" + "mnt-by: OWNER-MNT\n" + "created: 2022-08-14T11:48:28Z\n" + "last-modified: 2022-10-25T12:22:39Z\n" + "source: TEST"); + //Another branch databaseHelper.addObject("" + @@ -3456,4 +3679,232 @@ private void loadIpv4RelationTreeExample(){ ipTreeUpdater.rebuild(); } + + + private void loadIpv4RelationDomainExample(){ + databaseHelper.addObject("" + + "inetnum: 192.0.0.0 - 192.0.255.255\n" + + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: ALLOCATED PA\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "domain: 0.192.in-addr.arpa\n" + + "descr: Test domain\n" + + "admin-c: TP1-TEST\n" + + "tech-c: TP1-TEST\n" + + "zone-c: TP1-TEST\n" + + "notify: notify@test.net.au\n" + + "nserver: ns1.test.com.au 10.0.0.1\n" + + "nserver: ns2.test.com.au 2001:10::2\n" + + "ds-rdata: 52151 1 1 13ee60f7499a70e5aadaf05828e7fc59e8e70bc1\n" + + "ds-rdata: 17881 5 1 2e58131e5fe28ec965a7b8e4efb52d0a028d7a78\n" + + "ds-rdata: 17881 5 2 8c6265733a73e5588bfac516a4fcfbe1103a544b95f254cb67a21e474079547e\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2011-07-28T00:35:42Z\n" + + "last-modified: 2019-02-28T10:14:46Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "domain: 2.0.192.in-addr.arpa\n" + + "descr: Test domain\n" + + "admin-c: TP1-TEST\n" + + "tech-c: TP1-TEST\n" + + "zone-c: TP1-TEST\n" + + "notify: notify@test.net.au\n" + + "nserver: ns1.test.com.au 10.0.0.1\n" + + "nserver: ns2.test.com.au 2001:10::2\n" + + "ds-rdata: 52151 1 1 13ee60f7499a70e5aadaf05828e7fc59e8e70bc1\n" + + "ds-rdata: 17881 5 1 2e58131e5fe28ec965a7b8e4efb52d0a028d7a78\n" + + "ds-rdata: 17881 5 2 8c6265733a73e5588bfac516a4fcfbe1103a544b95f254cb67a21e474079547e\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2011-07-28T00:35:42Z\n" + + "last-modified: 2019-02-28T10:14:46Z\n" + + "source: TEST"); + + + databaseHelper.addObject("" + + "domain: 1.2.0.192.in-addr.arpa\n" + + "descr: Test domain\n" + + "admin-c: TP1-TEST\n" + + "tech-c: TP1-TEST\n" + + "zone-c: TP1-TEST\n" + + "notify: notify@test.net.au\n" + + "nserver: ns1.test.com.au 10.0.0.1\n" + + "nserver: ns2.test.com.au 2001:10::2\n" + + "ds-rdata: 52151 1 1 13ee60f7499a70e5aadaf05828e7fc59e8e70bc1\n" + + "ds-rdata: 17881 5 1 2e58131e5fe28ec965a7b8e4efb52d0a028d7a78\n" + + "ds-rdata: 17881 5 2 8c6265733a73e5588bfac516a4fcfbe1103a544b95f254cb67a21e474079547e\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2011-07-28T00:35:42Z\n" + + "last-modified: 2019-02-28T10:14:46Z\n" + + "source: TEST"); + + ipTreeUpdater.rebuild(); + } + + private void loadIpv6RelationTreeExample(){ + /* + +--------------+ + | /0 | + +--------------+ + / \ + +--------------+ +----------------+ + | FC00::/7 | | 2000::/3 | + +--------------+ +----------------+ + / \ / | | \ + +-----------------+ +-------------------+ +------------+ +-----------+ +-----------+ +-----------+ + | FC00::/8 | | FD00::/8 | | 2001::/16 | | 2400::/12 | | 2600::/12 | | 2800::/12 | + +-----------------+ +-------------------+ +------------+ +-----------+ +-----------+ +-----------+ + / | + +--------------+ +--------------+ + | 2001:db8::/32 | | 2001::/23 | + +--------------+ +--------------+ + */ + + databaseHelper.addObject("" + + "inet6num: 2000::/3\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + // One branch + databaseHelper.addObject("" + + "inet6num: 2001::/16\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + databaseHelper.addObject("" + + "inet6num: 2001:db8::/32\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + databaseHelper.addObject("" + + "inet6num: 2001::/23\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + databaseHelper.addObject("" + + "inet6num: 2400::/12\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inet6num: 2600::/12\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inet6num: 2800::/12\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + + databaseHelper.addObject("" + + "inet6num: FC00::/7\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inet6num: FC00::/8\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + databaseHelper.addObject("" + + "inet6num: FD00::/8\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-LIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + ipTreeUpdater.rebuild(); + } } From 243efbbd28a49908e8018abbe5905575c3f718c7 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 4 Dec 2024 15:56:42 +0100 Subject: [PATCH 06/34] feat: remove unecessary changes --- .../java/net/ripe/db/whois/api/rdap/RdapRelationService.java | 2 +- .../net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java | 1 - .../java/net/ripe/db/whois/common/etree/NestedIntervalMap.java | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index c3db254253..8c242bd692 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -155,6 +155,7 @@ private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterv private boolean isOutOfRegionOrRoot(final IpEntry firstLessSpecific) { final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); if (rpslObject == null) { + LOGGER.error("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return true; } final CIString status = rpslObject.getValueForAttribute(AttributeType.STATUS); @@ -166,7 +167,6 @@ private boolean isOutOfRegionOrRoot(final IpEntry firstLessSpecific) { private RpslObject getResourceByKey(final String key){ final RpslObject rpslObject = rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); if (rpslObject == null){ - LOGGER.error("INET(6)NUM {} does not exist in RIPE Database ", key); return rpslObjectDao.getByKeyOrNull(ObjectType.INETNUM, key); } return rpslObject; diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java index 43caa7f097..521c24954d 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java @@ -3,7 +3,6 @@ import jakarta.ws.rs.ext.ParamConverter; import jakarta.ws.rs.ext.ParamConverterProvider; import jakarta.ws.rs.ext.Provider; -import net.ripe.db.whois.api.rdap.domain.RdapRequestType; import net.ripe.db.whois.api.rdap.domain.RelationType; import org.springframework.stereotype.Component; diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java index 4cc63e680a..763dc932fd 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java @@ -2,10 +2,7 @@ import com.google.common.collect.Lists; import net.ripe.db.whois.common.collect.CollectionHelper; -import net.ripe.db.whois.common.domain.IpRanges; import net.ripe.db.whois.common.ip.Interval; -import net.ripe.db.whois.common.ip.IpInterval; -import net.ripe.db.whois.common.rpsl.AttributeParser; import org.apache.commons.lang.Validate; import java.util.ArrayList; From 68a345a7dbc2f691fed3f8f8719c3b448c059c45 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 11 Dec 2024 10:45:52 +0100 Subject: [PATCH 07/34] feat: enable status query parameter --- .../whois/api/rdap/RdapRelationService.java | 55 +++++++++++++------ .../ripe/db/whois/api/rdap/RdapService.java | 8 +-- .../common/rpsl/attrs/Inet6numStatus.java | 2 +- .../whois/common/rpsl/attrs/InetStatus.java | 2 +- .../common/rpsl/attrs/InetnumStatus.java | 2 +- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 8c242bd692..513e8cec3b 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -1,6 +1,7 @@ package net.ripe.db.whois.api.rdap; import com.google.common.collect.Sets; +import io.netty.util.internal.StringUtil; import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.common.dao.RpslObjectDao; import net.ripe.db.whois.common.domain.CIString; @@ -22,6 +23,7 @@ import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Nullable; @@ -40,21 +42,24 @@ public class RdapRelationService { private final Ipv4DomainTree ipv4DomainTree; private final Ipv6DomainTree ipv6DomainTree; private final RpslObjectDao rpslObjectDao; + private final String whoisSource; public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, - final RpslObjectDao rpslObjectDao) { + final RpslObjectDao rpslObjectDao, + @Value("${whois.source}") final String whoisSource) { this.ip4Tree = ip4Tree; this.ip6Tree = ip6Tree; this.ipv4DomainTree = ipv4DomainTree; this.ipv6DomainTree = ipv6DomainTree; this.rpslObjectDao = rpslObjectDao; + this.whoisSource = whoisSource; } - public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ + public List getDomainRelationPkeys(final String pkey, final RelationType relationType, final String status){ final Domain domain = Domain.parse(pkey); final IpInterval reverseIp = domain.getReverseIp(); - final List ipEntries = getIpEntries(getIpDomainTree(reverseIp), relationType, reverseIp); + final List ipEntries = getIpEntries(getIpDomainTree(reverseIp), relationType, reverseIp, status); return ipEntries .stream() @@ -62,16 +67,17 @@ public List getDomainRelationPkeys(final String pkey, final RelationType .toList(); } - public List getInetnumRelationPkeys(final String pkey, final RelationType relationType){ + public List getInetnumRelationPkeys(final String pkey, final RelationType relationType, final String status){ final IpInterval ip = IpInterval.parse(pkey); - final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip); + final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip, status); return ipEntries.stream().map(ipEntry -> ipEntry.getKey().toString()).toList(); } - private List getIpEntries(final IpTree ipTree, final RelationType relationType, final IpInterval reverseIp) { + private List getIpEntries(final IpTree ipTree, final RelationType relationType, + final IpInterval reverseIp, final String status) { return switch (relationType) { - case UP -> List.of(searchFirstLessSpecificCoMntner(ipTree, reverseIp)); - case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp); + case UP -> List.of(searchFirstLessSpecificCoMntner(ipTree, reverseIp, status)); + case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp, status); case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); case BOTTOM -> searchMostSpecificFillingOverlaps(ipTree, reverseIp); }; @@ -134,33 +140,50 @@ private static List findSiblingsAndExact(final IpTree ipTree, final IpI return ipTree.findFirstMoreSpecific(IpInterval.parse(parentList.getFirst().getKey().toString())); } - private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp){ + private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp, final String status){ final List parentList = ipTree.findFirstLessSpecific(reverseIp); - if (parentList.isEmpty() || isOutOfRegionOrRoot(parentList.getFirst())){ + if (parentList.isEmpty() || !isRequestedResource(parentList.getFirst(), status)){ throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } return parentList.getFirst(); } - private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp) { + private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp, final String status) { for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { final IpEntry ipEntry = (IpEntry) parentEntry; - if (!isOutOfRegionOrRoot(ipEntry)){ + if (isRequestedResource(ipEntry, status)){ return List.of(ipEntry); } } throw new RdapException("404 Not Found", "No top level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } - private boolean isOutOfRegionOrRoot(final IpEntry firstLessSpecific) { + private boolean isRequestedResource(final IpEntry firstLessSpecific, final String status){ final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); if (rpslObject == null) { LOGGER.error("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); + return false; + } + + if (isOutOfRegion(rpslObject)){ + return false; + } + + if ((StringUtil.isNullOrEmpty(status) || status.equals("active")) && !isAdministrativeResource(rpslObject)){ return true; } - final CIString status = rpslObject.getValueForAttribute(AttributeType.STATUS); - return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(status).isOutOfRegionOrRoot()) - || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(status).isOutOfRegionOrRoot()); + return (!StringUtil.isNullOrEmpty(status) && status.equals("inactive")) && isAdministrativeResource(rpslObject); + } + + private boolean isOutOfRegion(final RpslObject rpslObject){ + final CIString source = rpslObject.getValueForAttribute(AttributeType.SOURCE); + return !source.toString().equals(whoisSource); + } + + private boolean isAdministrativeResource(final RpslObject rpslObject) { + final CIString statusAttributeValue = rpslObject.getValueForAttribute(AttributeType.STATUS); + return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(statusAttributeValue).isAdministrativeResource()) + || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(statusAttributeValue).isAdministrativeResource()); } @Nullable diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 14f378314a..18e52f6afb 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -263,7 +263,7 @@ public Response relationSearch( throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); } - final List rpslObjects = handleRelationQuery(request, requestType, relationType, key); + final List rpslObjects = handleRelationQuery(request, requestType, relationType, key, status); return Response.ok(rdapObjectMapper.mapSearch( getRequestUrl(request), @@ -516,13 +516,13 @@ private String objectTypesToString(final Collection objectTypes) { } private List handleRelationQuery(final HttpServletRequest request, final RdapRequestType requestType, - final RelationType relationType, final String key) { + final RelationType relationType, final String key, final String status) { final List rpslObjects; switch (requestType) { case AUTNUMS -> throw new RdapException("400 Bad Request", "Relation queries not allowed for autnum", HttpStatus.BAD_REQUEST_400); case DOMAINS -> { rdapRequestValidator.validateDomain(key); - final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); + final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType, status); rpslObjects = relatedPkeys .stream() @@ -531,7 +531,7 @@ private List handleRelationQuery(final HttpServletRequest request, f } case IPS -> { rdapRequestValidator.validateIp(request.getRequestURI(), key); - final List relatedPkeys = rdapRelationService.getInetnumRelationPkeys(key, relationType); + final List relatedPkeys = rdapRelationService.getInetnumRelationPkeys(key, relationType, status); rpslObjects = relatedPkeys .stream() diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java index 2e9975560e..c9ac622e77 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java @@ -81,7 +81,7 @@ public boolean worksWithParentInHierarchy(final InetStatus parentInHierarchyMain } @Override - public boolean isOutOfRegionOrRoot() { + public boolean isAdministrativeResource() { return this.equals(ALLOCATED_BY_RIR); } diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java index 60a3dbb134..4c164eef87 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java @@ -17,5 +17,5 @@ public interface InetStatus { boolean worksWithParentInHierarchy(InetStatus parentInHierarchyMaintainedByRs, boolean parentHasRsMntLower); - boolean isOutOfRegionOrRoot(); + boolean isAdministrativeResource(); } diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java index 243c6a64ad..ff0e96345d 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java @@ -108,7 +108,7 @@ public boolean worksWithParentInHierarchy(final InetStatus parentInHierarchyMain } @Override - public boolean isOutOfRegionOrRoot() { + public boolean isAdministrativeResource() { return this.equals(ALLOCATED_UNSPECIFIED); } From 8073771fe5364758e779ed3e48cef7220f49eb25 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 11 Dec 2024 12:37:24 +0100 Subject: [PATCH 08/34] feat: add Integration tests for status query parameter --- .../api/rdap/RdapServiceTestIntegration.java | 159 +++++++++++++++++- 1 file changed, 153 insertions(+), 6 deletions(-) diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index cf0c2bb060..beae8f2114 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3130,6 +3130,7 @@ public void get_help_response() { /*RIR Search*/ + //up @Test public void get_up_autnum_then_400(){ final BadRequestException badRequestException = assertThrows(BadRequestException.class, () -> { @@ -3156,6 +3157,50 @@ public void get_up_then_parent(){ assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); // /26 } + @Test + public void get_non_existing_up_then_404(){ + loadIpv4RelationTreeExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/24"); + } + + + @Test + public void get_up_inactive_status_then_administrative_parent_not_found(){ + loadIpv4RelationTreeExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/192.0.2.0/28?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/28"); + } + + @Test + public void get_up_inactive_status_then_administrative_parent(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.0.0/16?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("0.0.0.0 - 255.255.255.255")); + } + @Test public void get_ipv6_up_then_parent(){ loadIpv6RelationTreeExample(); @@ -3169,6 +3214,35 @@ public void get_ipv6_up_then_parent(){ assertThat(ipResults.getFirst().getHandle(), is("2001::/16")); } + @Test + public void get_ipv6_up_inactive_then_parent_not_found(){ + loadIpv6RelationTreeExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/2001:db8::/32?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 2001:db8::/32"); + } + + @Test + public void get_ipv6_up_inactive_then_parent(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/up/FC00::/7?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("::/0")); + } + + @Test public void get_domain_up_then_parent(){ loadIpv4RelationTreeExample(); @@ -3184,36 +3258,40 @@ public void get_domain_up_then_parent(){ } @Test - public void get_non_existing_domain_up_then_404(){ + public void get_domain_up_inactive_then_parent_not_found(){ loadIpv4RelationTreeExample(); loadIpv4RelationDomainExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("domains/rirSearch1/up/0.192.in-addr.arpa") + createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa?status=inactive") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); + assertErrorTitle(notFoundException, "404 Not Found"); assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 192.0.0.0/16"); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.1/32"); } @Test - public void get_non_existing_up_then_404(){ + public void get_non_existing_domain_up_then_404(){ loadIpv4RelationTreeExample(); + loadIpv4RelationDomainExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/192.0.2.0/24") + createResource("domains/rirSearch1/up/0.192.in-addr.arpa") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); assertErrorTitle(notFoundException, "404 Not Found"); assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/24"); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.0.0/16"); } + + // Top @Test public void get_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv4RelationTreeExample(); @@ -3270,6 +3348,34 @@ public void get_non_existing_domain_top_then_404(){ assertErrorDescription(notFoundException, "No top level object has been found for 192.0.0.0/16"); } + @Test + public void get_ipv6_top_not_found(){ + loadIpv6RelationTreeExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/top/2000::/3") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No top level object has been found for 2000::/3"); + } + + @Test + public void get_ipv6_inactive_then_top_found(){ + loadIpv6RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/top/2000::/3?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("::/0")); + } + @Test public void get_non_existing_top_then_404(){ loadIpv4RelationTreeExample(); @@ -3285,6 +3391,20 @@ public void get_non_existing_top_then_404(){ assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); } + @Test + public void get_inactive_top_then_parent(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/24?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("0.0.0.0 - 255.255.255.255")); + } + + // Bottom @Test public void get_bottom_then_bottom(){ loadIpv4RelationTreeExample(); @@ -3408,6 +3528,20 @@ public void get_bottom_wrong_type_then_400(){ assertErrorDescription(badRequestException, "Invalid or unknown type ip"); } + @Test + public void bottom_with_status_then_501(){ + + final ServerErrorException notImplementedException = assertThrows(ServerErrorException.class, () -> { + createResource("ip/rirSearch1/bottom/192.0.2.0/24?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + assertErrorTitle(notImplementedException, "501 Not Implemented"); + assertErrorStatus(notImplementedException, HttpStatus.NOT_IMPLEMENTED_501); + assertErrorDescription(notImplementedException, "Status is not implement in down and bottom relation"); + } + + // Down @Test public void get_down_then_immediate_child(){ loadIpv4RelationTreeExample(); @@ -3477,6 +3611,19 @@ public void get_down_when_no_child_then_empty_response(){ assertThat(ipResults, is(nullValue())); } + @Test + public void down_with_status_then_501(){ + + final ServerErrorException notImplementedException = assertThrows(ServerErrorException.class, () -> { + createResource("ip/rirSearch1/down/192.0.2.0/24?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + assertErrorTitle(notImplementedException, "501 Not Implemented"); + assertErrorStatus(notImplementedException, HttpStatus.NOT_IMPLEMENTED_501); + assertErrorDescription(notImplementedException, "Status is not implement in down and bottom relation"); + } + /* Helper methods*/ private void assertCommon(RdapObject object) { From 4b687b70619de9b72f969da103deae5963d063e7 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 11 Dec 2024 12:40:57 +0100 Subject: [PATCH 09/34] feat: add test for status active --- .../whois/api/rdap/RdapServiceTestIntegration.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index beae8f2114..4a285015f5 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3157,6 +3157,19 @@ public void get_up_then_parent(){ assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); // /26 } + @Test + public void get_up_active_status_then_parent(){ + loadIpv4RelationTreeExample(); + + final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28?status=active") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); // /26 + } + @Test public void get_non_existing_up_then_404(){ loadIpv4RelationTreeExample(); From 9ae6dd233b2e5ceeea5a54f7a7c96665c0c4b565 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 11 Dec 2024 15:37:46 +0100 Subject: [PATCH 10/34] feat: add redirect, and TODOS --- .../whois/api/rdap/RdapRelationService.java | 21 +---- .../ripe/db/whois/api/rdap/RdapService.java | 10 ++- .../api/rdap/domain/RdapRequestType.java | 2 +- .../api/rdap/RdapServiceTestIntegration.java | 85 ++++++++++++------- 4 files changed, 69 insertions(+), 49 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 513e8cec3b..7ec5f87f45 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -1,7 +1,6 @@ package net.ripe.db.whois.api.rdap; import com.google.common.collect.Sets; -import io.netty.util.internal.StringUtil; import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.common.dao.RpslObjectDao; import net.ripe.db.whois.common.domain.CIString; @@ -42,18 +41,15 @@ public class RdapRelationService { private final Ipv4DomainTree ipv4DomainTree; private final Ipv6DomainTree ipv6DomainTree; private final RpslObjectDao rpslObjectDao; - private final String whoisSource; public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, - final RpslObjectDao rpslObjectDao, - @Value("${whois.source}") final String whoisSource) { + final RpslObjectDao rpslObjectDao) { this.ip4Tree = ip4Tree; this.ip6Tree = ip6Tree; this.ipv4DomainTree = ipv4DomainTree; this.ipv6DomainTree = ipv6DomainTree; this.rpslObjectDao = rpslObjectDao; - this.whoisSource = whoisSource; } public List getDomainRelationPkeys(final String pkey, final RelationType relationType, final String status){ @@ -165,19 +161,10 @@ private boolean isRequestedResource(final IpEntry firstLessSpecific, final Strin return false; } - if (isOutOfRegion(rpslObject)){ - return false; - } - - if ((StringUtil.isNullOrEmpty(status) || status.equals("active")) && !isAdministrativeResource(rpslObject)){ - return true; + if (status.equals("inactive") && isAdministrativeResource(rpslObject)){ + return false; // TODO: We do not support administrative resources so far. Return true once we start supporting them } - return (!StringUtil.isNullOrEmpty(status) && status.equals("inactive")) && isAdministrativeResource(rpslObject); - } - - private boolean isOutOfRegion(final RpslObject rpslObject){ - final CIString source = rpslObject.getValueForAttribute(AttributeType.SOURCE); - return !source.toString().equals(whoisSource); + return status.equals("active") && !isAdministrativeResource(rpslObject); } private boolean isAdministrativeResource(final RpslObject rpslObject) { diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 18e52f6afb..74b097053e 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -5,6 +5,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; @@ -257,13 +258,18 @@ public Response relationSearch( @PathParam("objectType") RdapRequestType requestType, @PathParam("relation") RelationType relationType, @PathParam("key") final String key, - @QueryParam("status") final String status) { + @QueryParam("status") String status) { if (status != null && (relationType.equals(RelationType.DOWN) || relationType.equals(RelationType.BOTTOM))){ throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); } - final List rpslObjects = handleRelationQuery(request, requestType, relationType, key, status); + final Set objectTypes = requestType.getWhoisObjectTypes(key); + if (isRedirect(Iterables.getOnlyElement(objectTypes), key)) { + return redirect(getRequestPath(request), getQueryObject(objectTypes, key)); + } + + final List rpslObjects = handleRelationQuery(request, requestType, relationType, key, status == null ? "inactive" : status); return Response.ok(rdapObjectMapper.mapSearch( getRequestUrl(request), diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java index 97b27361ba..7ea08972a6 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RdapRequestType.java @@ -25,7 +25,7 @@ public Set getWhoisObjectTypes(final String key) { AUTNUMS { public Set getWhoisObjectTypes(final String key) { - return ImmutableSet.of(AUT_NUM, AS_BLOCK); + return ImmutableSet.of(AUT_NUM); } }, diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 4a285015f5..3d0fe04af6 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3148,7 +3148,7 @@ public void get_up_autnum_then_400(){ public void get_up_then_parent(){ loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28") + final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3175,7 +3175,7 @@ public void get_non_existing_up_then_404(){ loadIpv4RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/192.0.2.0/24") + createResource("ips/rirSearch1/up/192.0.2.0/24?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3201,24 +3201,42 @@ public void get_up_inactive_status_then_administrative_parent_not_found(){ assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/28"); } + @Test + public void get_up_default_value_status_then_administrative_parent_not_found(){ + loadIpv4RelationTreeExample(); + + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/192.0.2.0/28") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/28"); + } + @Test public void get_up_inactive_status_then_administrative_parent(){ loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.0.0/16?status=inactive") + // TODO: We do not support administrative resources, we return 404. Change this when we support them + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/192.0.0.0/16?status=inactive") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); + }); - final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("0.0.0.0 - 255.255.255.255")); + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for 192.0.0.0/16"); } @Test public void get_ipv6_up_then_parent(){ loadIpv6RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/2001:db8::/32") + final SearchResult searchResult = createResource("ips/rirSearch1/up/2001:db8::/32?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3246,13 +3264,16 @@ public void get_ipv6_up_inactive_then_parent_not_found(){ public void get_ipv6_up_inactive_then_parent(){ loadIpv6RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/FC00::/7?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); + // TODO: We do not support administrative resources, we return 404. Change this when we support them + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/up/FC00::/7?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); - final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("::/0")); + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No up level object has been found for fc00::/7"); } @@ -3261,7 +3282,7 @@ public void get_domain_up_then_parent(){ loadIpv4RelationTreeExample(); loadIpv4RelationDomainExample(); - final SearchResult searchResult = createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa") + final SearchResult searchResult = createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3309,7 +3330,7 @@ public void get_non_existing_domain_up_then_404(){ public void get_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28") + final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3322,7 +3343,7 @@ public void get_top_then_less_specific_allocated_assigned_first_parent(){ public void get_ipv6_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv6RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/top/2001:db8::/32") + final SearchResult searchResult = createResource("ips/rirSearch1/top/2001:db8::/32?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3336,7 +3357,7 @@ public void get_domain_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv4RelationTreeExample(); loadIpv4RelationDomainExample(); - final SearchResult searchResult = createResource("domains/rirSearch1/top/1.2.0.192.in-addr.arpa") + final SearchResult searchResult = createResource("domains/rirSearch1/top/1.2.0.192.in-addr.arpa?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3366,7 +3387,7 @@ public void get_ipv6_top_not_found(){ loadIpv6RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/top/2000::/3") + createResource("ips/rirSearch1/top/2000::/3?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3380,13 +3401,16 @@ public void get_ipv6_top_not_found(){ public void get_ipv6_inactive_then_top_found(){ loadIpv6RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/top/2000::/3?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); + // TODO: We do not support administrative resources, we return 404. Change this when we support them + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/top/2000::/3?status=inactive") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); - final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("::/0")); + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No top level object has been found for 2000::/3"); } @Test @@ -3394,7 +3418,7 @@ public void get_non_existing_top_then_404(){ loadIpv4RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/top/192.0.2.0/24") + createResource("ips/rirSearch1/top/192.0.2.0/24?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3408,13 +3432,16 @@ public void get_non_existing_top_then_404(){ public void get_inactive_top_then_parent(){ loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/24?status=inactive") + // TODO: We do not support administrative resources, we return 404. Change this when we support them + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource("ips/rirSearch1/top/192.0.2.0/24?status=inactive") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); + }); - final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("0.0.0.0 - 255.255.255.255")); + assertErrorTitle(notFoundException, "404 Not Found"); + assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); + assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); } // Bottom From ed3514d51af1b59a2f92e2ecf3e2ff123e4ffde3 Mon Sep 17 00:00:00 2001 From: mherran Date: Fri, 13 Dec 2024 10:31:50 +0100 Subject: [PATCH 11/34] feat: remove import --- .../java/net/ripe/db/whois/api/rdap/RdapRelationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 7ec5f87f45..bfe01bcb0d 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -22,7 +22,6 @@ import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.annotation.Nullable; From 3bfdb9cf476f4dfa77ec97d2d3975184f9b45079 Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 17 Dec 2024 11:23:04 +0100 Subject: [PATCH 12/34] feat: remove status and always return active resources --- .../whois/api/rdap/RdapRelationService.java | 32 ++-- .../ripe/db/whois/api/rdap/RdapService.java | 11 +- .../api/rdap/RdapServiceTestIntegration.java | 154 +----------------- 3 files changed, 30 insertions(+), 167 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index bfe01bcb0d..72c72c4c94 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -51,10 +51,10 @@ public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, this.rpslObjectDao = rpslObjectDao; } - public List getDomainRelationPkeys(final String pkey, final RelationType relationType, final String status){ + public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ final Domain domain = Domain.parse(pkey); final IpInterval reverseIp = domain.getReverseIp(); - final List ipEntries = getIpEntries(getIpDomainTree(reverseIp), relationType, reverseIp, status); + final List ipEntries = getIpEntries(getIpDomainTree(reverseIp), relationType, reverseIp); return ipEntries .stream() @@ -62,17 +62,17 @@ public List getDomainRelationPkeys(final String pkey, final RelationType .toList(); } - public List getInetnumRelationPkeys(final String pkey, final RelationType relationType, final String status){ + public List getInetnumRelationPkeys(final String pkey, final RelationType relationType){ final IpInterval ip = IpInterval.parse(pkey); - final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip, status); + final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip); return ipEntries.stream().map(ipEntry -> ipEntry.getKey().toString()).toList(); } private List getIpEntries(final IpTree ipTree, final RelationType relationType, - final IpInterval reverseIp, final String status) { + final IpInterval reverseIp) { return switch (relationType) { - case UP -> List.of(searchFirstLessSpecificCoMntner(ipTree, reverseIp, status)); - case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp, status); + case UP -> List.of(searchFirstLessSpecificCoMntner(ipTree, reverseIp)); + case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp); case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); case BOTTOM -> searchMostSpecificFillingOverlaps(ipTree, reverseIp); }; @@ -135,35 +135,31 @@ private static List findSiblingsAndExact(final IpTree ipTree, final IpI return ipTree.findFirstMoreSpecific(IpInterval.parse(parentList.getFirst().getKey().toString())); } - private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp, final String status){ + private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp){ final List parentList = ipTree.findFirstLessSpecific(reverseIp); - if (parentList.isEmpty() || !isRequestedResource(parentList.getFirst(), status)){ + if (parentList.isEmpty() || !resourceExist(parentList.getFirst())){ throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } return parentList.getFirst(); } - private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp, final String status) { + private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp) { for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { final IpEntry ipEntry = (IpEntry) parentEntry; - if (isRequestedResource(ipEntry, status)){ + if (resourceExist(ipEntry)){ return List.of(ipEntry); } } throw new RdapException("404 Not Found", "No top level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } - private boolean isRequestedResource(final IpEntry firstLessSpecific, final String status){ + private boolean resourceExist(final IpEntry firstLessSpecific){ final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); - if (rpslObject == null) { + if (rpslObject == null || isAdministrativeResource(rpslObject)) { LOGGER.error("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return false; } - - if (status.equals("inactive") && isAdministrativeResource(rpslObject)){ - return false; // TODO: We do not support administrative resources so far. Return true once we start supporting them - } - return status.equals("active") && !isAdministrativeResource(rpslObject); + return true; } private boolean isAdministrativeResource(final RpslObject rpslObject) { diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 74b097053e..5a99b45090 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -260,6 +260,9 @@ public Response relationSearch( @PathParam("key") final String key, @QueryParam("status") String status) { + //TODO: [MH] Status is being ignored until administrative resources are included in RDAP. If status is not + // given or status is inactive...include administrative resources in the output. However, if status is active + // return just non administrative resources, as we are doing now. if (status != null && (relationType.equals(RelationType.DOWN) || relationType.equals(RelationType.BOTTOM))){ throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); } @@ -269,7 +272,7 @@ public Response relationSearch( return redirect(getRequestPath(request), getQueryObject(objectTypes, key)); } - final List rpslObjects = handleRelationQuery(request, requestType, relationType, key, status == null ? "inactive" : status); + final List rpslObjects = handleRelationQuery(request, requestType, relationType, key); return Response.ok(rdapObjectMapper.mapSearch( getRequestUrl(request), @@ -522,13 +525,13 @@ private String objectTypesToString(final Collection objectTypes) { } private List handleRelationQuery(final HttpServletRequest request, final RdapRequestType requestType, - final RelationType relationType, final String key, final String status) { + final RelationType relationType, final String key) { final List rpslObjects; switch (requestType) { case AUTNUMS -> throw new RdapException("400 Bad Request", "Relation queries not allowed for autnum", HttpStatus.BAD_REQUEST_400); case DOMAINS -> { rdapRequestValidator.validateDomain(key); - final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType, status); + final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); rpslObjects = relatedPkeys .stream() @@ -537,7 +540,7 @@ private List handleRelationQuery(final HttpServletRequest request, f } case IPS -> { rdapRequestValidator.validateIp(request.getRequestURI(), key); - final List relatedPkeys = rdapRelationService.getInetnumRelationPkeys(key, relationType, status); + final List relatedPkeys = rdapRelationService.getInetnumRelationPkeys(key, relationType); rpslObjects = relatedPkeys .stream() diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 3d0fe04af6..9b6b78b823 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3148,20 +3148,7 @@ public void get_up_autnum_then_400(){ public void get_up_then_parent(){ loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28?status=active") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - - final List ipResults = searchResult.getIpSearchResults(); - assertThat(ipResults.size(), is(1)); - assertThat(ipResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); // /26 - } - - @Test - public void get_up_active_status_then_parent(){ - loadIpv4RelationTreeExample(); - - final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28?status=active") + final SearchResult searchResult = createResource("ips/rirSearch1/up/192.0.2.0/28") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3175,7 +3162,7 @@ public void get_non_existing_up_then_404(){ loadIpv4RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/192.0.2.0/24?status=active") + createResource("ips/rirSearch1/up/192.0.2.0/24") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3186,57 +3173,11 @@ public void get_non_existing_up_then_404(){ } - @Test - public void get_up_inactive_status_then_administrative_parent_not_found(){ - loadIpv4RelationTreeExample(); - - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/192.0.2.0/28?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/28"); - } - - @Test - public void get_up_default_value_status_then_administrative_parent_not_found(){ - loadIpv4RelationTreeExample(); - - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/192.0.2.0/28") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.0/28"); - } - - @Test - public void get_up_inactive_status_then_administrative_parent(){ - loadIpv4RelationTreeExample(); - - // TODO: We do not support administrative resources, we return 404. Change this when we support them - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/192.0.0.0/16?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 192.0.0.0/16"); - } - @Test public void get_ipv6_up_then_parent(){ loadIpv6RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/up/2001:db8::/32?status=active") + final SearchResult searchResult = createResource("ips/rirSearch1/up/2001:db8::/32") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3245,44 +3186,13 @@ public void get_ipv6_up_then_parent(){ assertThat(ipResults.getFirst().getHandle(), is("2001::/16")); } - @Test - public void get_ipv6_up_inactive_then_parent_not_found(){ - loadIpv6RelationTreeExample(); - - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/2001:db8::/32?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 2001:db8::/32"); - } - - @Test - public void get_ipv6_up_inactive_then_parent(){ - loadIpv6RelationTreeExample(); - - // TODO: We do not support administrative resources, we return 404. Change this when we support them - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/up/FC00::/7?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for fc00::/7"); - } - @Test public void get_domain_up_then_parent(){ loadIpv4RelationTreeExample(); loadIpv4RelationDomainExample(); - final SearchResult searchResult = createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa?status=active") + final SearchResult searchResult = createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3291,22 +3201,6 @@ public void get_domain_up_then_parent(){ assertThat(domainResults.getFirst().getHandle(), is("2.0.192.in-addr.arpa")); } - @Test - public void get_domain_up_inactive_then_parent_not_found(){ - loadIpv4RelationTreeExample(); - loadIpv4RelationDomainExample(); - - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("domains/rirSearch1/up/1.2.0.192.in-addr.arpa?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No up level object has been found for 192.0.2.1/32"); - } @Test public void get_non_existing_domain_up_then_404(){ @@ -3330,7 +3224,7 @@ public void get_non_existing_domain_up_then_404(){ public void get_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv4RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28?status=active") + final SearchResult searchResult = createResource("ips/rirSearch1/top/192.0.2.0/28") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3343,7 +3237,7 @@ public void get_top_then_less_specific_allocated_assigned_first_parent(){ public void get_ipv6_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv6RelationTreeExample(); - final SearchResult searchResult = createResource("ips/rirSearch1/top/2001:db8::/32?status=active") + final SearchResult searchResult = createResource("ips/rirSearch1/top/2001:db8::/32") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3357,7 +3251,7 @@ public void get_domain_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv4RelationTreeExample(); loadIpv4RelationDomainExample(); - final SearchResult searchResult = createResource("domains/rirSearch1/top/1.2.0.192.in-addr.arpa?status=active") + final SearchResult searchResult = createResource("domains/rirSearch1/top/1.2.0.192.in-addr.arpa") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3387,7 +3281,7 @@ public void get_ipv6_top_not_found(){ loadIpv6RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/top/2000::/3?status=active") + createResource("ips/rirSearch1/top/2000::/3") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3397,28 +3291,13 @@ public void get_ipv6_top_not_found(){ assertErrorDescription(notFoundException, "No top level object has been found for 2000::/3"); } - @Test - public void get_ipv6_inactive_then_top_found(){ - loadIpv6RelationTreeExample(); - - // TODO: We do not support administrative resources, we return 404. Change this when we support them - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/top/2000::/3?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No top level object has been found for 2000::/3"); - } @Test public void get_non_existing_top_then_404(){ loadIpv4RelationTreeExample(); final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/top/192.0.2.0/24?status=active") + createResource("ips/rirSearch1/top/192.0.2.0/24") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3428,21 +3307,6 @@ public void get_non_existing_top_then_404(){ assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); } - @Test - public void get_inactive_top_then_parent(){ - loadIpv4RelationTreeExample(); - - // TODO: We do not support administrative resources, we return 404. Change this when we support them - final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource("ips/rirSearch1/top/192.0.2.0/24?status=inactive") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(SearchResult.class); - }); - - assertErrorTitle(notFoundException, "404 Not Found"); - assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); - } // Bottom @Test From 708eb3784cfa3bc01a9b395e5c18701939571361 Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 17 Dec 2024 12:25:33 +0100 Subject: [PATCH 13/34] feat: create a wrong relation type IT --- .../whois/api/rdap/RdapServiceTestIntegration.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 9b6b78b823..349d691f03 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3130,6 +3130,20 @@ public void get_help_response() { /*RIR Search*/ + @Test + public void get_invalid_relation_autnum_then_400(){ + final BadRequestException badRequestException = assertThrows(BadRequestException.class, () -> { + createResource("autnums/rirSearch1/upper/AS123") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + }); + + assertErrorTitle(badRequestException, "400 Bad Request"); + assertErrorStatus(badRequestException, HttpStatus.BAD_REQUEST_400); + assertErrorDescription(badRequestException, "unknown relation"); + } + + //up @Test public void get_up_autnum_then_400(){ From 3f1838af8aead1cbbace56b5527d68f398e0d674 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 18 Dec 2024 11:39:52 +0100 Subject: [PATCH 14/34] feat: add a comment into the readme --- README.RDAP.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.RDAP.md b/README.RDAP.md index 11c9f81844..183832e100 100644 --- a/README.RDAP.md +++ b/README.RDAP.md @@ -116,3 +116,8 @@ then a 404 is returned. An object with "administrative" status is never returned Currently, IANA allocations are not present in the RIPE database, but just out-of-region placeholders. Refer to [NRO RDAP](https://bitbucket.org/nroecg/nro-rdap-profile/raw/v1/nro-rdap-profile.txt) Profile section 4.5. "Status" + +Relation Searches doesn't consider the "status" query parameter +----------------------------------------------------------------- +This is related to the previous point. An object with "administrative" status is never returned. +Therefore, only "active" (non-administrative) objects are taking into account when using relation searches. From a392f1980e11219b1a3d886435c823cad9967392 Mon Sep 17 00:00:00 2001 From: mherran Date: Wed, 18 Dec 2024 11:57:11 +0100 Subject: [PATCH 15/34] feat: apply PR comments --- .../whois/api/rdap/RdapRelationService.java | 29 ++++++++++--------- .../common/rpsl/attrs/Inet6numStatus.java | 5 ---- .../whois/common/rpsl/attrs/InetStatus.java | 2 -- .../common/rpsl/attrs/InetnumStatus.java | 5 ---- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 72c72c4c94..ac8d42aebe 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -29,6 +29,9 @@ import java.util.Objects; import java.util.Set; +import static net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus.ALLOCATED_BY_RIR; +import static net.ripe.db.whois.common.rpsl.attrs.InetnumStatus.ALLOCATED_UNSPECIFIED; + @Service public class RdapRelationService { @@ -65,14 +68,17 @@ public List getDomainRelationPkeys(final String pkey, final RelationType public List getInetnumRelationPkeys(final String pkey, final RelationType relationType){ final IpInterval ip = IpInterval.parse(pkey); final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip); - return ipEntries.stream().map(ipEntry -> ipEntry.getKey().toString()).toList(); + return ipEntries + .stream() + .map(ipEntry -> ipEntry.getKey().toString()) + .toList(); } private List getIpEntries(final IpTree ipTree, final RelationType relationType, final IpInterval reverseIp) { return switch (relationType) { - case UP -> List.of(searchFirstLessSpecificCoMntner(ipTree, reverseIp)); - case TOP -> searchCoMntnerTopLevel(ipTree, reverseIp); + case UP -> List.of(searchFirstLessSpecific(ipTree, reverseIp)); + case TOP -> searchTopLevel(ipTree, reverseIp); case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); case BOTTOM -> searchMostSpecificFillingOverlaps(ipTree, reverseIp); }; @@ -82,11 +88,7 @@ private List searchMostSpecificFillingOverlaps(final IpTree ipTree, fin final List mostSpecificValues = ipTree.findMostSpecific(reverseIp); final List> ipResources = mostSpecificValues.stream().map(ip -> IpInterval.parse(ip.getKey().toString())).toList(); final Set mostSpecificFillingOverlaps = Sets.newConcurrentHashSet(); - - for (int countIps = 0; countIps < mostSpecificValues.size(); countIps++){ - final IpInterval firstResource = ipResources.get(countIps); - processChildren(ipTree, reverseIp, firstResource, mostSpecificFillingOverlaps); - } + ipResources.forEach(ipResource -> processChildren(ipTree, reverseIp, ipResource, mostSpecificFillingOverlaps)); return mostSpecificFillingOverlaps.stream().toList(); } @@ -135,7 +137,7 @@ private static List findSiblingsAndExact(final IpTree ipTree, final IpI return ipTree.findFirstMoreSpecific(IpInterval.parse(parentList.getFirst().getKey().toString())); } - private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInterval reverseIp){ + private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval reverseIp){ final List parentList = ipTree.findFirstLessSpecific(reverseIp); if (parentList.isEmpty() || !resourceExist(parentList.getFirst())){ throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); @@ -143,7 +145,7 @@ private IpEntry searchFirstLessSpecificCoMntner(final IpTree ipTree, final IpInt return parentList.getFirst(); } - private List searchCoMntnerTopLevel(final IpTree ipTree, final IpInterval reverseIp) { + private List searchTopLevel(final IpTree ipTree, final IpInterval reverseIp) { for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { final IpEntry ipEntry = (IpEntry) parentEntry; if (resourceExist(ipEntry)){ @@ -162,12 +164,13 @@ private boolean resourceExist(final IpEntry firstLessSpecific){ return true; } - private boolean isAdministrativeResource(final RpslObject rpslObject) { + private static boolean isAdministrativeResource(final RpslObject rpslObject) { final CIString statusAttributeValue = rpslObject.getValueForAttribute(AttributeType.STATUS); - return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(statusAttributeValue).isAdministrativeResource()) - || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(statusAttributeValue).isAdministrativeResource()); + return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_UNSPECIFIED)) + || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_BY_RIR)); } + @Nullable private RpslObject getResourceByKey(final String key){ final RpslObject rpslObject = rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java index c9ac622e77..d3422508fa 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java @@ -80,11 +80,6 @@ public boolean worksWithParentInHierarchy(final InetStatus parentInHierarchyMain return true; } - @Override - public boolean isAdministrativeResource() { - return this.equals(ALLOCATED_BY_RIR); - } - @Override public boolean needsOrgReference() { return NEEDS_ORG_REFERENCE.contains(this); diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java index 4c164eef87..3e39023beb 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetStatus.java @@ -16,6 +16,4 @@ public interface InetStatus { boolean worksWithParentStatus(InetStatus parent, boolean objectHasRsMaintainer); boolean worksWithParentInHierarchy(InetStatus parentInHierarchyMaintainedByRs, boolean parentHasRsMntLower); - - boolean isAdministrativeResource(); } diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java index ff0e96345d..811480c301 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/InetnumStatus.java @@ -107,11 +107,6 @@ public boolean worksWithParentInHierarchy(final InetStatus parentInHierarchyMain return true; } - @Override - public boolean isAdministrativeResource() { - return this.equals(ALLOCATED_UNSPECIFIED); - } - @Override public boolean needsOrgReference() { return NEEDS_ORG_REFERENCE.contains(this); From 4dc2b2036f3431c6e7bbe340e50674d3f58bcb1a Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 12:53:29 +0100 Subject: [PATCH 16/34] feat: use stringUtils and update Interval java doc --- .../src/main/java/net/ripe/db/whois/api/rdap/RdapService.java | 4 ++-- .../main/java/net/ripe/db/whois/common/etree/IntervalMap.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 74131b03c1..1b815c2a01 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -4,8 +4,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; +import io.netty.util.internal.StringUtil; import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.DefaultValue; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; @@ -263,7 +263,7 @@ public Response relationSearch( //TODO: [MH] Status is being ignored until administrative resources are included in RDAP. If status is not // given or status is inactive...include administrative resources in the output. However, if status is active // return just non administrative resources, as we are doing now. - if (status != null && (relationType.equals(RelationType.DOWN) || relationType.equals(RelationType.BOTTOM))){ + if (!StringUtil.isNullOrEmpty(status) && (relationType.equals(RelationType.DOWN) || relationType.equals(RelationType.BOTTOM))){ throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java index 10062cef6e..878035d137 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/IntervalMap.java @@ -164,7 +164,7 @@ public interface IntervalMap, V> { /** * Finds all values associated with intervals that are contained within (more specific than) - * and whose prefix is higher, more specific + * and whose prefix is most high * key. *

*

From 75f4af48466481495f445f68c8456a8cbdea5943 Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 14:48:17 +0100 Subject: [PATCH 17/34] feat: remove Type converter and do the logic in the enum --- .../api/rdap/RdapRelationTypeConverter.java | 34 ------------------- .../ripe/db/whois/api/rdap/RdapService.java | 11 ++++-- .../whois/api/rdap/RdapServletDeployer.java | 8 ++--- .../whois/api/rdap/domain/RelationType.java | 9 +++++ 4 files changed, 19 insertions(+), 43 deletions(-) delete mode 100644 whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java deleted file mode 100644 index 521c24954d..0000000000 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationTypeConverter.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.ripe.db.whois.api.rdap; - -import jakarta.ws.rs.ext.ParamConverter; -import jakarta.ws.rs.ext.ParamConverterProvider; -import jakarta.ws.rs.ext.Provider; -import net.ripe.db.whois.api.rdap.domain.RelationType; -import org.springframework.stereotype.Component; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; - -@Component -@Provider -public class RdapRelationTypeConverter implements ParamConverterProvider { - - @Override - public ParamConverter getConverter(Class rawType, Type type, Annotation[] annotations) { - if(!rawType.equals(RelationType.class)) { - return null; - } - - return (ParamConverter) new ParamConverter() { - @Override - public RelationType fromString(String relationType){ - return RelationType.valueOf(relationType.toUpperCase()); - } - - @Override - public String toString(RelationType relationType){ - return relationType.toString(); - } - }; - } -} diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 1b815c2a01..ace90f3cba 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -256,14 +256,19 @@ public Response help(@Context final HttpServletRequest request) { public Response relationSearch( @Context final HttpServletRequest request, @PathParam("objectType") RdapRequestType requestType, - @PathParam("relation") RelationType relationType, + @PathParam("relation") String relationType, @PathParam("key") final String key, @QueryParam("status") String status) { + final RelationType relation = RelationType.fromString(relationType); //TODO: [MH] Status is being ignored until administrative resources are included in RDAP. If status is not // given or status is inactive...include administrative resources in the output. However, if status is active // return just non administrative resources, as we are doing now. - if (!StringUtil.isNullOrEmpty(status) && (relationType.equals(RelationType.DOWN) || relationType.equals(RelationType.BOTTOM))){ + if (!StringUtil.isNullOrEmpty(status) && status.equalsIgnoreCase("inactive")) { + throw new RdapException("501 Not Implemented", "Inactive status is not implemented", HttpStatus.NOT_IMPLEMENTED_501); + } + + if (!StringUtil.isNullOrEmpty(status) && (relation.equals(RelationType.DOWN) || relation.equals(RelationType.BOTTOM))){ throw new RdapException("501 Not Implemented", "Status is not implement in down and bottom relation", HttpStatus.NOT_IMPLEMENTED_501); } @@ -272,7 +277,7 @@ public Response relationSearch( return redirect(getRequestPath(request), getQueryObject(objectTypes, key)); } - final List rpslObjects = handleRelationQuery(request, requestType, relationType, key); + final List rpslObjects = handleRelationQuery(request, requestType, relation, key); return Response.ok(rdapObjectMapper.mapSearch( getRequestUrl(request), diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java index 5922373bad..1045496808 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapServletDeployer.java @@ -1,6 +1,7 @@ package net.ripe.db.whois.api.rdap; import com.fasterxml.jackson.databind.SerializationFeature; +import jakarta.servlet.DispatcherType; import net.ripe.db.whois.api.httpserver.ServletDeployer; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletHolder; @@ -11,7 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import jakarta.servlet.DispatcherType; import java.util.EnumSet; @Component @@ -20,17 +20,14 @@ public class RdapServletDeployer implements ServletDeployer { private final RdapService rdapService; private final RdapExceptionMapper rdapExceptionMapper; private final RdapRequestTypeConverter rdapRequestTypeConverter; - private final RdapRelationTypeConverter rdapRelationTypeConverter; @Autowired public RdapServletDeployer(final RdapService rdapService, final RdapExceptionMapper rdapExceptionMapper, - final RdapRequestTypeConverter rdapRequestTypeConverter, - final RdapRelationTypeConverter rdapRelationTypeConverter) { + final RdapRequestTypeConverter rdapRequestTypeConverter) { this.rdapService = rdapService; this.rdapExceptionMapper = rdapExceptionMapper; this.rdapRequestTypeConverter = rdapRequestTypeConverter; - this.rdapRelationTypeConverter = rdapRelationTypeConverter; } @Override @@ -48,7 +45,6 @@ public void deploy(final WebAppContext context) { resourceConfig.register(rdapService); resourceConfig.register(rdapRequestTypeConverter); resourceConfig.register(rdapExceptionMapper); - resourceConfig.register(rdapRelationTypeConverter); resourceConfig.register(rdapJsonProvider); context.addServlet(new ServletHolder("Whois RDAP REST API", new ServletContainer(resourceConfig)), "/rdap/*"); } diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java index 6dfb8a5127..0ceef2d4f3 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java @@ -15,4 +15,13 @@ public enum RelationType { public String getValue() { return value; } + + public static RelationType fromString(final String value) { + for (RelationType type : RelationType.values()) { + if (type.name().equalsIgnoreCase(value)) { + return type; + } + } + throw new IllegalArgumentException("No enum constant for value: " + value); + } } From fdd0782b00523299b988e83ea5e807e713353a10 Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 15:57:44 +0100 Subject: [PATCH 18/34] feat: refactors --- .../whois/api/rdap/RdapRelationService.java | 37 +++++++++---------- .../whois/api/rdap/domain/RelationType.java | 5 ++- .../api/rdap/RdapServiceTestIntegration.java | 12 +++--- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index ac8d42aebe..c0c095f3bc 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -22,11 +22,13 @@ import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import static net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus.ALLOCATED_BY_RIR; @@ -44,6 +46,7 @@ public class RdapRelationService { private final Ipv6DomainTree ipv6DomainTree; private final RpslObjectDao rpslObjectDao; + @Autowired public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, final RpslObjectDao rpslObjectDao) { @@ -92,7 +95,7 @@ private List searchMostSpecificFillingOverlaps(final IpTree ipTree, fin return mostSpecificFillingOverlaps.stream().toList(); } - private static void processChildren(final IpTree ipTree, final IpInterval reverseIp, + private void processChildren(final IpTree ipTree, final IpInterval reverseIp, final IpInterval mostSpecificResource, final Set mostSpecificFillingOverlaps) { @@ -146,19 +149,20 @@ private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval re } private List searchTopLevel(final IpTree ipTree, final IpInterval reverseIp) { - for (final Object parentEntry : ipTree.findAllLessSpecific(reverseIp)) { - final IpEntry ipEntry = (IpEntry) parentEntry; - if (resourceExist(ipEntry)){ - return List.of(ipEntry); - } - } - throw new RdapException("404 Not Found", "No top level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + final Optional optionalIpEntry = ipTree.findAllLessSpecific(reverseIp).stream() + .filter(entry -> resourceExist((IpEntry) entry)) + .findAny(); + + return List.of(optionalIpEntry.orElseThrow(() -> + new RdapException("404 Not Found", "No top-level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404) + )); + } private boolean resourceExist(final IpEntry firstLessSpecific){ final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); if (rpslObject == null || isAdministrativeResource(rpslObject)) { - LOGGER.error("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); + LOGGER.debug("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return false; } return true; @@ -173,30 +177,23 @@ private static boolean isAdministrativeResource(final RpslObject rpslObject) { @Nullable private RpslObject getResourceByKey(final String key){ - final RpslObject rpslObject = rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); - if (rpslObject == null){ + if (IpInterval.parse(key) instanceof Ipv4Resource){ return rpslObjectDao.getByKeyOrNull(ObjectType.INETNUM, key); } - return rpslObject; + return rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); } private IpTree getIpTree(final IpInterval reverseIp) { if (reverseIp instanceof Ipv4Resource) { return ip4Tree; - } else if (reverseIp instanceof Ipv6Resource) { - return ip6Tree; } - - throw new IllegalArgumentException("Unexpected reverse ip: " + reverseIp); + return ip6Tree; } private IpTree getIpDomainTree(final IpInterval reverseIp) { if (reverseIp instanceof Ipv4Resource) { return ipv4DomainTree; - } else if (reverseIp instanceof Ipv6Resource) { - return ipv6DomainTree; } - - throw new IllegalArgumentException("Unexpected reverse ip: " + reverseIp); + return ipv6DomainTree; } } diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java index 0ceef2d4f3..c08c1405fe 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/domain/RelationType.java @@ -1,5 +1,8 @@ package net.ripe.db.whois.api.rdap.domain; +import net.ripe.db.whois.api.rdap.RdapException; +import org.eclipse.jetty.http.HttpStatus; + public enum RelationType { UP("up"), TOP("top"), @@ -22,6 +25,6 @@ public static RelationType fromString(final String value) { return type; } } - throw new IllegalArgumentException("No enum constant for value: " + value); + throw new RdapException("400 Bad Request", "Relation " + value + " doesn't exist", HttpStatus.BAD_REQUEST_400); } } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 349d691f03..196d6e0aa9 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3140,7 +3140,7 @@ public void get_invalid_relation_autnum_then_400(){ assertErrorTitle(badRequestException, "400 Bad Request"); assertErrorStatus(badRequestException, HttpStatus.BAD_REQUEST_400); - assertErrorDescription(badRequestException, "unknown relation"); + assertErrorDescription(badRequestException, "Relation upper doesn't exist"); } @@ -3287,7 +3287,7 @@ public void get_non_existing_domain_top_then_404(){ assertErrorTitle(notFoundException, "404 Not Found"); assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No top level object has been found for 192.0.0.0/16"); + assertErrorDescription(notFoundException, "No top-level object has been found for 192.0.0.0/16"); } @Test @@ -3302,7 +3302,7 @@ public void get_ipv6_top_not_found(){ assertErrorTitle(notFoundException, "404 Not Found"); assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No top level object has been found for 2000::/3"); + assertErrorDescription(notFoundException, "No top-level object has been found for 2000::/3"); } @@ -3318,7 +3318,7 @@ public void get_non_existing_top_then_404(){ assertErrorTitle(notFoundException, "404 Not Found"); assertErrorStatus(notFoundException, HttpStatus.NOT_FOUND_404); - assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); + assertErrorDescription(notFoundException, "No top-level object has been found for 192.0.2.0/24"); } @@ -3450,7 +3450,7 @@ public void get_bottom_wrong_type_then_400(){ public void bottom_with_status_then_501(){ final ServerErrorException notImplementedException = assertThrows(ServerErrorException.class, () -> { - createResource("ip/rirSearch1/bottom/192.0.2.0/24?status=inactive") + createResource("ip/rirSearch1/bottom/192.0.2.0/24?status=active") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3539,7 +3539,7 @@ public void down_with_status_then_501(){ }); assertErrorTitle(notImplementedException, "501 Not Implemented"); assertErrorStatus(notImplementedException, HttpStatus.NOT_IMPLEMENTED_501); - assertErrorDescription(notImplementedException, "Status is not implement in down and bottom relation"); + assertErrorDescription(notImplementedException, "Inactive status is not implemented"); } /* Helper methods*/ From 6cad06dca0f27b5a17c0420ad8f4738fa76adc01 Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 16:19:13 +0100 Subject: [PATCH 19/34] feat: rename --- .../ripe/db/whois/api/rdap/RdapRelationService.java | 10 +++++----- .../db/whois/api/rdap/RdapServiceTestIntegration.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index c0c095f3bc..11f4c9d8ea 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -91,13 +91,13 @@ private List searchMostSpecificFillingOverlaps(final IpTree ipTree, fin final List mostSpecificValues = ipTree.findMostSpecific(reverseIp); final List> ipResources = mostSpecificValues.stream().map(ip -> IpInterval.parse(ip.getKey().toString())).toList(); final Set mostSpecificFillingOverlaps = Sets.newConcurrentHashSet(); - ipResources.forEach(ipResource -> processChildren(ipTree, reverseIp, ipResource, mostSpecificFillingOverlaps)); + ipResources.forEach(ipResource -> extractBottomMatches(ipTree, reverseIp, ipResource, mostSpecificFillingOverlaps)); return mostSpecificFillingOverlaps.stream().toList(); } - private void processChildren(final IpTree ipTree, final IpInterval reverseIp, - final IpInterval mostSpecificResource, - final Set mostSpecificFillingOverlaps) { + private void extractBottomMatches(final IpTree ipTree, final IpInterval reverseIp, + final IpInterval mostSpecificResource, + final Set mostSpecificFillingOverlaps) { final List siblingsAndExact = findSiblingsAndExact(ipTree, mostSpecificResource); @@ -115,7 +115,7 @@ private void processChildren(final IpTree ipTree, final IpInterval reverseIp, if (!parentInterval.equals(reverseIp) && !childrenCoverParentRange(firstSibling, lastSibling, parentInterval)){ - processChildren(ipTree, reverseIp, parentInterval, mostSpecificFillingOverlaps); + extractBottomMatches(ipTree, reverseIp, parentInterval, mostSpecificFillingOverlaps); } } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 196d6e0aa9..6353fa5930 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3146,16 +3146,16 @@ public void get_invalid_relation_autnum_then_400(){ //up @Test - public void get_up_autnum_then_400(){ - final BadRequestException badRequestException = assertThrows(BadRequestException.class, () -> { + public void get_up_autnum_then_501(){ + final ServerErrorException notImplementedException = assertThrows(ServerErrorException.class, () -> { createResource("autnums/rirSearch1/up/AS123") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); - assertErrorTitle(badRequestException, "400 Bad Request"); - assertErrorStatus(badRequestException, HttpStatus.BAD_REQUEST_400); - assertErrorDescription(badRequestException, "Relation queries not allowed for autnum"); + assertErrorTitle(notImplementedException, "501 Not Implemented"); + assertErrorStatus(notImplementedException, HttpStatus.NOT_IMPLEMENTED_501);; + assertErrorDescription(notImplementedException, "Relation queries not allowed for autnum"); } @Test From dba7ce97ebfc2ba2da60e3d22adbcf056c219237 Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 16:26:51 +0100 Subject: [PATCH 20/34] feat: remove duplicated ; --- .../net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 6353fa5930..f56645e8bf 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3154,7 +3154,7 @@ public void get_up_autnum_then_501(){ }); assertErrorTitle(notImplementedException, "501 Not Implemented"); - assertErrorStatus(notImplementedException, HttpStatus.NOT_IMPLEMENTED_501);; + assertErrorStatus(notImplementedException, HttpStatus.NOT_IMPLEMENTED_501); assertErrorDescription(notImplementedException, "Relation queries not allowed for autnum"); } From cb869d39ddaa43b0d695314719ccfd48c656619d Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 16:30:10 +0100 Subject: [PATCH 21/34] feat: return not implemented for autnums --- .../src/main/java/net/ripe/db/whois/api/rdap/RdapService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index ace90f3cba..226c95107e 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -533,7 +533,7 @@ private List handleRelationQuery(final HttpServletRequest request, f final RelationType relationType, final String key) { final List rpslObjects; switch (requestType) { - case AUTNUMS -> throw new RdapException("400 Bad Request", "Relation queries not allowed for autnum", HttpStatus.BAD_REQUEST_400); + case AUTNUMS -> throw new RdapException("501 Not Implemented", "Relation queries not allowed for autnum", HttpStatus.NOT_IMPLEMENTED_501); case DOMAINS -> { rdapRequestValidator.validateDomain(key); final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); From 0ed5ab7d3c7da79bc6a1e713efea9daad4a9eee2 Mon Sep 17 00:00:00 2001 From: mherran Date: Mon, 13 Jan 2025 17:59:28 +0100 Subject: [PATCH 22/34] feat: us up from top to recursive search and inet6num logic --- .../whois/api/rdap/RdapRelationService.java | 42 ++++++++++++------- .../api/rdap/RdapServiceTestIntegration.java | 39 +++++++++++++++++ .../common/rpsl/attrs/Inet6numStatus.java | 4 ++ 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 11f4c9d8ea..df76d149ef 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -28,7 +28,6 @@ import javax.annotation.Nullable; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Set; import static net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus.ALLOCATED_BY_RIR; @@ -81,7 +80,7 @@ private List getIpEntries(final IpTree ipTree, final RelationType relat final IpInterval reverseIp) { return switch (relationType) { case UP -> List.of(searchFirstLessSpecific(ipTree, reverseIp)); - case TOP -> searchTopLevel(ipTree, reverseIp); + case TOP -> searchTopLevelResource(ipTree, reverseIp); case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); case BOTTOM -> searchMostSpecificFillingOverlaps(ipTree, reverseIp); }; @@ -142,36 +141,51 @@ private static List findSiblingsAndExact(final IpTree ipTree, final IpI private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval reverseIp){ final List parentList = ipTree.findFirstLessSpecific(reverseIp); - if (parentList.isEmpty() || !resourceExist(parentList.getFirst())){ + if (parentList.isEmpty() || !resourceExist(reverseIp, parentList.getFirst())){ throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); } return parentList.getFirst(); } - private List searchTopLevel(final IpTree ipTree, final IpInterval reverseIp) { - final Optional optionalIpEntry = ipTree.findAllLessSpecific(reverseIp).stream() - .filter(entry -> resourceExist((IpEntry) entry)) - .findAny(); + private List searchTopLevelResource(final IpTree ipTree, final IpInterval reverseIp){ + IpEntry ipEntry; + try { + ipEntry = searchFirstLessSpecific(ipTree, reverseIp); + } catch (RdapException ex){ + throw new RdapException("404 Not Found", "No top-level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + } - return List.of(optionalIpEntry.orElseThrow(() -> - new RdapException("404 Not Found", "No top-level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404) - )); + return List.of(loopUpLevels(ipTree, ipEntry)); + } + private IpEntry loopUpLevels(final IpTree ipTree, IpEntry reverseIp) { + try { + reverseIp = searchFirstLessSpecific(ipTree, (IpInterval) reverseIp.getKey()); + loopUpLevels(ipTree, reverseIp); + } catch (RdapException ex){ + /* + * Do Nothing, end of loop + * */ + } + return reverseIp; } - private boolean resourceExist(final IpEntry firstLessSpecific){ + private boolean resourceExist(final IpInterval ip, final IpEntry firstLessSpecific){ + final RpslObject children = getResourceByKey(ip.toString()); final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); - if (rpslObject == null || isAdministrativeResource(rpslObject)) { + if (rpslObject == null || isAdministrativeResource(children, rpslObject)) { LOGGER.debug("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return false; } return true; } - private static boolean isAdministrativeResource(final RpslObject rpslObject) { + private static boolean isAdministrativeResource(final RpslObject children, final RpslObject rpslObject) { + final CIString childrenStatus = children.getValueForAttribute(AttributeType.STATUS); final CIString statusAttributeValue = rpslObject.getValueForAttribute(AttributeType.STATUS); return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_UNSPECIFIED)) - || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_BY_RIR)); + || (rpslObject.getType().equals(ObjectType.INET6NUM) && !Inet6numStatus.getStatusFor(childrenStatus).isAssignment() && + Inet6numStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_BY_RIR)); } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index f56645e8bf..de1aaa3e94 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3305,6 +3305,32 @@ public void get_ipv6_top_not_found(){ assertErrorDescription(notFoundException, "No top-level object has been found for 2000::/3"); } + @Test + public void get_ipv6_top_found_if_assignment(){ + loadIpv6RelationTreeExample(); + + databaseHelper.updateObject("" + + "inet6num: 2000::/3\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ASSIGNED\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + + final SearchResult searchResult = createResource("ips/rirSearch1/top/2000::/3") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List ipResults = searchResult.getIpSearchResults(); + assertThat(ipResults.size(), is(1)); + assertThat(ipResults.getFirst().getHandle(), is("::/0")); + } + @Test public void get_non_existing_top_then_404(){ @@ -3760,6 +3786,19 @@ private void loadIpv4RelationDomainExample(){ "last-modified: 2022-10-25T12:22:39Z\n" + "source: TEST"); + databaseHelper.addObject("" + + "inetnum: 192.0.2.1 - 192.0.2.1\n" + + "netname: TEST-NET-NAME\n" + + "descr: TEST network\n" + + "country: NL\n" + + "language: en\n" + + "tech-c: TP1-TEST\n" + + "status: ALLOCATED PA\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + databaseHelper.addObject("" + "domain: 0.192.in-addr.arpa\n" + "descr: Test domain\n" + diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java index d3422508fa..806b699bfe 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java @@ -99,4 +99,8 @@ public boolean isValidOrgType(final OrgType orgType) { public String toString() { return literalStatus.toString(); } + + public boolean isAssignment(){ + return this.equals(ASSIGNED) || this.equals(ASSIGNED_ANYCAST) || this.equals(ASSIGNED_PI); + } } From c1ee7ea3d4fe5ef898fe3e65d8a4e166203a70f5 Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 14 Jan 2025 11:14:48 +0100 Subject: [PATCH 23/34] feat: clarify logic --- .../whois/api/rdap/RdapRelationService.java | 79 +++++++++---------- .../api/rdap/RdapServiceTestIntegration.java | 14 ++++ .../common/rpsl/attrs/Inet6numStatus.java | 4 - 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index df76d149ef..1c47a4322d 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -77,44 +77,40 @@ public List getInetnumRelationPkeys(final String pkey, final RelationTyp } private List getIpEntries(final IpTree ipTree, final RelationType relationType, - final IpInterval reverseIp) { + final IpInterval searchIp) { return switch (relationType) { - case UP -> List.of(searchFirstLessSpecific(ipTree, reverseIp)); - case TOP -> searchTopLevelResource(ipTree, reverseIp); - case DOWN -> ipTree.findFirstMoreSpecific(reverseIp); - case BOTTOM -> searchMostSpecificFillingOverlaps(ipTree, reverseIp); + case UP -> List.of(searchFirstLessSpecific(ipTree, searchIp)); + case TOP -> List.of(searchTopLevelResource(ipTree, searchIp)); + case DOWN -> ipTree.findFirstMoreSpecific(searchIp); + case BOTTOM -> searchBottomResources(ipTree, searchIp); }; } - private List searchMostSpecificFillingOverlaps(final IpTree ipTree, final IpInterval reverseIp){ - final List mostSpecificValues = ipTree.findMostSpecific(reverseIp); - final List> ipResources = mostSpecificValues.stream().map(ip -> IpInterval.parse(ip.getKey().toString())).toList(); + private List searchBottomResources(final IpTree ipTree, final IpInterval searchIp){ + final List mostSpecificValues = ipTree.findMostSpecific(searchIp); final Set mostSpecificFillingOverlaps = Sets.newConcurrentHashSet(); - ipResources.forEach(ipResource -> extractBottomMatches(ipTree, reverseIp, ipResource, mostSpecificFillingOverlaps)); + mostSpecificValues.forEach(ipResource -> extractBottomMatches(ipTree, searchIp, ipResource, mostSpecificFillingOverlaps)); return mostSpecificFillingOverlaps.stream().toList(); } - private void extractBottomMatches(final IpTree ipTree, final IpInterval reverseIp, - final IpInterval mostSpecificResource, + private void extractBottomMatches(final IpTree ipTree, final IpInterval searchIp, + final IpEntry mostSpecificResource, final Set mostSpecificFillingOverlaps) { + mostSpecificFillingOverlaps.add(mostSpecificResource); - final List siblingsAndExact = findSiblingsAndExact(ipTree, mostSpecificResource); - - final IpEntry firstResourceIpEntry = siblingsAndExact.stream() - .filter(sibling -> sibling.getKey().toString().equals(mostSpecificResource.toString())) - .findFirst().orElse(null); - - mostSpecificFillingOverlaps.add(firstResourceIpEntry); + final IpInterval mostSpecificInterval = IpInterval.parse(mostSpecificResource.getKey().toString()); + final List parentList = ipTree.findFirstLessSpecific(mostSpecificInterval); + final List siblingsAndExact = findSiblingsAndExact(ipTree, mostSpecificInterval, parentList); final IpInterval firstSibling = (IpInterval)siblingsAndExact.getFirst().getKey(); final IpInterval lastSibling = (IpInterval)siblingsAndExact.getLast().getKey(); - final IpEntry parent = (IpEntry) ipTree.findFirstLessSpecific(IpInterval.parse(mostSpecificResource.toString())).getFirst(); + final IpEntry parent = parentList.getFirst(); final IpInterval parentInterval = (IpInterval) parent.getKey(); - if (!parentInterval.equals(reverseIp) && + if (!parentInterval.equals(searchIp) && // If the parent is already the search ip we stop !childrenCoverParentRange(firstSibling, lastSibling, parentInterval)){ - extractBottomMatches(ipTree, reverseIp, parentInterval, mostSpecificFillingOverlaps); + extractBottomMatches(ipTree, searchIp, parent, mostSpecificFillingOverlaps); } } @@ -131,47 +127,46 @@ private static boolean childrenCoverParentRange(final IpInterval firstResource, return Objects.equals(ipv6Resource.begin(), parentIpv6Resource.begin()) && Objects.equals(lastIpv6Resource.end(), parentIpv6Resource.end()); } - private static List findSiblingsAndExact(final IpTree ipTree, final IpInterval parentResource) { - final List parentList = ipTree.findFirstLessSpecific(parentResource); - if (parentList.isEmpty()){ + private static List findSiblingsAndExact(final IpTree ipTree, final IpInterval parentResource, final List parent) { + if (parent.isEmpty()){ return ipTree.findExact(parentResource); } - return ipTree.findFirstMoreSpecific(IpInterval.parse(parentList.getFirst().getKey().toString())); + return ipTree.findFirstMoreSpecific(IpInterval.parse(parent.getFirst().getKey().toString())); } - private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval reverseIp){ - final List parentList = ipTree.findFirstLessSpecific(reverseIp); - if (parentList.isEmpty() || !resourceExist(reverseIp, parentList.getFirst())){ - throw new RdapException("404 Not Found", "No up level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval searchIp){ + final List parentList = ipTree.findFirstLessSpecific(searchIp); + if (parentList.isEmpty() || !resourceExist(searchIp, parentList.getFirst())){ + throw new RdapException("404 Not Found", "No up level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); } return parentList.getFirst(); } - private List searchTopLevelResource(final IpTree ipTree, final IpInterval reverseIp){ + private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval searchIp){ IpEntry ipEntry; try { - ipEntry = searchFirstLessSpecific(ipTree, reverseIp); + ipEntry = searchFirstLessSpecific(ipTree, searchIp); } catch (RdapException ex){ - throw new RdapException("404 Not Found", "No top-level object has been found for " + reverseIp.toString(), HttpStatus.NOT_FOUND_404); + throw new RdapException("404 Not Found", "No top-level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); } - return List.of(loopUpLevels(ipTree, ipEntry)); + return loopUpLevels(ipTree, ipEntry); } - private IpEntry loopUpLevels(final IpTree ipTree, IpEntry reverseIp) { + private IpEntry loopUpLevels(final IpTree ipTree, IpEntry searchIp) { try { - reverseIp = searchFirstLessSpecific(ipTree, (IpInterval) reverseIp.getKey()); - loopUpLevels(ipTree, reverseIp); + searchIp = searchFirstLessSpecific(ipTree, (IpInterval) searchIp.getKey()); + loopUpLevels(ipTree, searchIp); } catch (RdapException ex){ /* * Do Nothing, end of loop * */ } - return reverseIp; + return searchIp; } - private boolean resourceExist(final IpInterval ip, final IpEntry firstLessSpecific){ - final RpslObject children = getResourceByKey(ip.toString()); + private boolean resourceExist(final IpInterval searchIp, final IpEntry firstLessSpecific){ + final RpslObject children = getResourceByKey(searchIp.toString()); final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); if (rpslObject == null || isAdministrativeResource(children, rpslObject)) { LOGGER.debug("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); @@ -184,7 +179,7 @@ private static boolean isAdministrativeResource(final RpslObject children, final final CIString childrenStatus = children.getValueForAttribute(AttributeType.STATUS); final CIString statusAttributeValue = rpslObject.getValueForAttribute(AttributeType.STATUS); return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_UNSPECIFIED)) - || (rpslObject.getType().equals(ObjectType.INET6NUM) && !Inet6numStatus.getStatusFor(childrenStatus).isAssignment() && + || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(childrenStatus).equals(ALLOCATED_BY_RIR) && Inet6numStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_BY_RIR)); } @@ -197,8 +192,8 @@ private RpslObject getResourceByKey(final String key){ return rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); } - private IpTree getIpTree(final IpInterval reverseIp) { - if (reverseIp instanceof Ipv4Resource) { + private IpTree getIpTree(final IpInterval searchIp) { + if (searchIp instanceof Ipv4Resource) { return ip4Tree; } return ip6Tree; diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index de1aaa3e94..21776674f5 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3294,6 +3294,20 @@ public void get_non_existing_domain_top_then_404(){ public void get_ipv6_top_not_found(){ loadIpv6RelationTreeExample(); + //ALLOCATED-BY-RIR -> ALLOCATED-BY-RIR when the child and parent has this status, parent is administrative + databaseHelper.updateObject("" + + "inet6num: 2000::/3\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-RIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + final NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { createResource("ips/rirSearch1/top/2000::/3") .request(MediaType.APPLICATION_JSON_TYPE) diff --git a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java index 806b699bfe..d3422508fa 100644 --- a/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java +++ b/whois-rpsl/src/main/java/net/ripe/db/whois/common/rpsl/attrs/Inet6numStatus.java @@ -99,8 +99,4 @@ public boolean isValidOrgType(final OrgType orgType) { public String toString() { return literalStatus.toString(); } - - public boolean isAssignment(){ - return this.equals(ASSIGNED) || this.equals(ASSIGNED_ANYCAST) || this.equals(ASSIGNED_PI); - } } From 0102f05ef9646522520fcf4607f9cb86b5019a7a Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 14 Jan 2025 11:18:02 +0100 Subject: [PATCH 24/34] feat: refactor parent cover logic --- .../db/whois/api/rdap/RdapRelationService.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 1c47a4322d..1e1cf1e91b 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -6,7 +6,6 @@ import net.ripe.db.whois.common.domain.CIString; import net.ripe.db.whois.common.ip.IpInterval; import net.ripe.db.whois.common.ip.Ipv4Resource; -import net.ripe.db.whois.common.ip.Ipv6Resource; import net.ripe.db.whois.common.iptree.IpEntry; import net.ripe.db.whois.common.iptree.IpTree; import net.ripe.db.whois.common.iptree.Ipv4DomainTree; @@ -27,7 +26,6 @@ import javax.annotation.Nullable; import java.util.List; -import java.util.Objects; import java.util.Set; import static net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus.ALLOCATED_BY_RIR; @@ -115,16 +113,8 @@ private void extractBottomMatches(final IpTree ipTree, final IpInterval searchIp } private static boolean childrenCoverParentRange(final IpInterval firstResource, final IpInterval lastResource, final IpInterval parent){ - if (firstResource instanceof Ipv4Resource ipv4Resource){ - final Ipv4Resource lastIpv4Resource = (Ipv4Resource)lastResource; - final Ipv4Resource parentIpv4Resource = (Ipv4Resource)parent; - return ipv4Resource.begin() == parentIpv4Resource.begin() && lastIpv4Resource.end() == parentIpv4Resource.end(); - } - - final Ipv6Resource ipv6Resource = (Ipv6Resource)firstResource; - final Ipv6Resource lastIpv6Resource = (Ipv6Resource)lastResource; - final Ipv6Resource parentIpv6Resource = (Ipv6Resource)parent; - return Objects.equals(ipv6Resource.begin(), parentIpv6Resource.begin()) && Objects.equals(lastIpv6Resource.end(), parentIpv6Resource.end()); + return firstResource.beginAsInetAddress().equals(parent.beginAsInetAddress()) && + lastResource.endAsInetAddress().equals(parent.endAsInetAddress()); } private static List findSiblingsAndExact(final IpTree ipTree, final IpInterval parentResource, final List parent) { From b000aeaadda201053c6869384bc465a25e92017c Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 14 Jan 2025 13:18:17 +0100 Subject: [PATCH 25/34] feat: return if no parent for bottm --- .../ripe/db/whois/api/rdap/RdapRelationService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 1e1cf1e91b..4d768b4a00 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -94,11 +94,17 @@ private List searchBottomResources(final IpTree ipTree, final IpInterva private void extractBottomMatches(final IpTree ipTree, final IpInterval searchIp, final IpEntry mostSpecificResource, final Set mostSpecificFillingOverlaps) { + mostSpecificFillingOverlaps.add(mostSpecificResource); final IpInterval mostSpecificInterval = IpInterval.parse(mostSpecificResource.getKey().toString()); final List parentList = ipTree.findFirstLessSpecific(mostSpecificInterval); - final List siblingsAndExact = findSiblingsAndExact(ipTree, mostSpecificInterval, parentList); + + if (parentList.isEmpty()){ + return; + } + + final List siblingsAndExact = findSiblingsAndExact(ipTree, parentList); final IpInterval firstSibling = (IpInterval)siblingsAndExact.getFirst().getKey(); final IpInterval lastSibling = (IpInterval)siblingsAndExact.getLast().getKey(); @@ -117,10 +123,7 @@ private static boolean childrenCoverParentRange(final IpInterval firstResource, lastResource.endAsInetAddress().equals(parent.endAsInetAddress()); } - private static List findSiblingsAndExact(final IpTree ipTree, final IpInterval parentResource, final List parent) { - if (parent.isEmpty()){ - return ipTree.findExact(parentResource); - } + private static List findSiblingsAndExact(final IpTree ipTree, final List parent) { return ipTree.findFirstMoreSpecific(IpInterval.parse(parent.getFirst().getKey().toString())); } From 359b45f44f8d1dc2edfcde0c3028c9ed7d49d36f Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 14 Jan 2025 14:43:23 +0100 Subject: [PATCH 26/34] feat: add a TODO for transforming inetnums into reverse --- .../db/whois/api/rdap/RdapRelationService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 4d768b4a00..597738ff9e 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -57,9 +57,10 @@ public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ final Domain domain = Domain.parse(pkey); final IpInterval reverseIp = domain.getReverseIp(); - final List ipEntries = getIpEntries(getIpDomainTree(reverseIp), relationType, reverseIp); + final List domainEntries = getEntries(getIpDomainTree(reverseIp), relationType, reverseIp); - return ipEntries + //TODO: [MH] This call should not be necessary, we should be able to get the reverseIp out of the IP + return domainEntries .stream() .map(ipEntry -> rpslObjectDao.getById(ipEntry.getObjectId()).getKey().toString()) .toList(); @@ -67,15 +68,15 @@ public List getDomainRelationPkeys(final String pkey, final RelationType public List getInetnumRelationPkeys(final String pkey, final RelationType relationType){ final IpInterval ip = IpInterval.parse(pkey); - final List ipEntries = getIpEntries(getIpTree(ip), relationType, ip); + final List ipEntries = getEntries(getIpTree(ip), relationType, ip); return ipEntries .stream() .map(ipEntry -> ipEntry.getKey().toString()) .toList(); } - private List getIpEntries(final IpTree ipTree, final RelationType relationType, - final IpInterval searchIp) { + private List getEntries(final IpTree ipTree, final RelationType relationType, + final IpInterval searchIp) { return switch (relationType) { case UP -> List.of(searchFirstLessSpecific(ipTree, searchIp)); case TOP -> List.of(searchTopLevelResource(ipTree, searchIp)); @@ -129,7 +130,7 @@ private static List findSiblingsAndExact(final IpTree ipTree, final Lis private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval searchIp){ final List parentList = ipTree.findFirstLessSpecific(searchIp); - if (parentList.isEmpty() || !resourceExist(searchIp, parentList.getFirst())){ + if (parentList.isEmpty() || !existAndNoAdministrative(searchIp, parentList.getFirst())){ throw new RdapException("404 Not Found", "No up level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); } return parentList.getFirst(); @@ -158,7 +159,7 @@ private IpEntry loopUpLevels(final IpTree ipTree, IpEntry searchIp) { return searchIp; } - private boolean resourceExist(final IpInterval searchIp, final IpEntry firstLessSpecific){ + private boolean existAndNoAdministrative(final IpInterval searchIp, final IpEntry firstLessSpecific){ final RpslObject children = getResourceByKey(searchIp.toString()); final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); if (rpslObject == null || isAdministrativeResource(children, rpslObject)) { From c092bea1a2eb2a098127c5dfa272f2f18b1ad262 Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 14 Jan 2025 15:06:40 +0100 Subject: [PATCH 27/34] feat: refactor --- .../net/ripe/db/whois/api/rdap/RdapRelationService.java | 8 +++----- .../net/ripe/db/whois/common/etree/NestedIntervalMap.java | 7 +------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 597738ff9e..a2135c4472 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -137,14 +137,12 @@ private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval se } private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval searchIp){ - IpEntry ipEntry; try { - ipEntry = searchFirstLessSpecific(ipTree, searchIp); + IpEntry ipEntry = searchFirstLessSpecific(ipTree, searchIp); + return loopUpLevels(ipTree, ipEntry); } catch (RdapException ex){ throw new RdapException("404 Not Found", "No top-level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); } - - return loopUpLevels(ipTree, ipEntry); } private IpEntry loopUpLevels(final IpTree ipTree, IpEntry searchIp) { @@ -162,7 +160,7 @@ private IpEntry loopUpLevels(final IpTree ipTree, IpEntry searchIp) { private boolean existAndNoAdministrative(final IpInterval searchIp, final IpEntry firstLessSpecific){ final RpslObject children = getResourceByKey(searchIp.toString()); final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); - if (rpslObject == null || isAdministrativeResource(children, rpslObject)) { + if (children == null || rpslObject == null || isAdministrativeResource(children, rpslObject)) { LOGGER.debug("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return false; } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java index 763dc932fd..a1f00945bc 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/etree/NestedIntervalMap.java @@ -233,12 +233,7 @@ private List> internalFindFirstMoreSpecific(K range) { } private List> internalFindMostSpecific(K range){ - final List> result = internalFindAllMoreSpecific(range); - if (result.isEmpty()){ - return Lists.newArrayList(); - } - - return result.parallelStream() + return internalFindAllMoreSpecific(range).parallelStream() .filter( kvInternalNode -> kvInternalNode.getChildren().isEmpty()) .toList(); } From 871aa2089d87a8e5834c706c14cca601ccd5de1b Mon Sep 17 00:00:00 2001 From: mherran Date: Tue, 14 Jan 2025 15:20:06 +0100 Subject: [PATCH 28/34] feat: clarify top search --- .../db/whois/api/rdap/RdapRelationService.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index a2135c4472..0debc76ee1 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -78,7 +78,7 @@ public List getInetnumRelationPkeys(final String pkey, final RelationTyp private List getEntries(final IpTree ipTree, final RelationType relationType, final IpInterval searchIp) { return switch (relationType) { - case UP -> List.of(searchFirstLessSpecific(ipTree, searchIp)); + case UP -> List.of(searchUpResource(ipTree, searchIp)); case TOP -> List.of(searchTopLevelResource(ipTree, searchIp)); case DOWN -> ipTree.findFirstMoreSpecific(searchIp); case BOTTOM -> searchBottomResources(ipTree, searchIp); @@ -128,7 +128,7 @@ private static List findSiblingsAndExact(final IpTree ipTree, final Lis return ipTree.findFirstMoreSpecific(IpInterval.parse(parent.getFirst().getKey().toString())); } - private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval searchIp){ + private IpEntry searchUpResource(final IpTree ipTree, final IpInterval searchIp){ final List parentList = ipTree.findFirstLessSpecific(searchIp); if (parentList.isEmpty() || !existAndNoAdministrative(searchIp, parentList.getFirst())){ throw new RdapException("404 Not Found", "No up level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); @@ -138,17 +138,17 @@ private IpEntry searchFirstLessSpecific(final IpTree ipTree, final IpInterval se private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval searchIp){ try { - IpEntry ipEntry = searchFirstLessSpecific(ipTree, searchIp); - return loopUpLevels(ipTree, ipEntry); + IpEntry ipEntry = searchUpResource(ipTree, searchIp); + return searchTopRemainingResource(ipTree, ipEntry); } catch (RdapException ex){ throw new RdapException("404 Not Found", "No top-level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); } } - private IpEntry loopUpLevels(final IpTree ipTree, IpEntry searchIp) { + private IpEntry searchTopRemainingResource(final IpTree ipTree, IpEntry searchIp) { try { - searchIp = searchFirstLessSpecific(ipTree, (IpInterval) searchIp.getKey()); - loopUpLevels(ipTree, searchIp); + searchIp = searchUpResource(ipTree, (IpInterval) searchIp.getKey()); + searchTopRemainingResource(ipTree, searchIp); } catch (RdapException ex){ /* * Do Nothing, end of loop From 17a2b44bed6e210dc4abd257897213ce09f4e979 Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 16 Jan 2025 13:55:18 +0100 Subject: [PATCH 29/34] feat: change top logic, go from less specific to more specific --- .../whois/api/rdap/RdapRelationService.java | 51 ++++++++----------- .../ripe/db/whois/api/rdap/RdapService.java | 2 +- .../api/rdap/RdapServiceTestIntegration.java | 13 +++++ 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 0debc76ee1..ca990c6599 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -137,51 +137,44 @@ private IpEntry searchUpResource(final IpTree ipTree, final IpInterval searchIp) } private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval searchIp){ - try { - IpEntry ipEntry = searchUpResource(ipTree, searchIp); - return searchTopRemainingResource(ipTree, ipEntry); - } catch (RdapException ex){ - throw new RdapException("404 Not Found", "No top-level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); + final List lessAndExact = ipTree.findExactAndAllLessSpecific(searchIp); + final List less = ipTree.findAllLessSpecific(searchIp); + + for (int countLessSpecific = 0; countLessSpecific < less.size(); countLessSpecific++){ + final IpEntry ipEntry = lessAndExact.get(countLessSpecific); + final IpInterval childIpInterval = (IpInterval)lessAndExact.get(countLessSpecific+1).getKey(); + if (existAndNoAdministrative(childIpInterval, ipEntry)){ + return ipEntry; + } } - } - - private IpEntry searchTopRemainingResource(final IpTree ipTree, IpEntry searchIp) { - try { - searchIp = searchUpResource(ipTree, (IpInterval) searchIp.getKey()); - searchTopRemainingResource(ipTree, searchIp); - } catch (RdapException ex){ - /* - * Do Nothing, end of loop - * */ - } - return searchIp; + throw new RdapException("404 Not Found", "No top-level object has been found for " + searchIp.toString(), HttpStatus.NOT_FOUND_404); } private boolean existAndNoAdministrative(final IpInterval searchIp, final IpEntry firstLessSpecific){ - final RpslObject children = getResourceByKey(searchIp.toString()); - final RpslObject rpslObject = getResourceByKey(firstLessSpecific.getKey().toString()); - if (children == null || rpslObject == null || isAdministrativeResource(children, rpslObject)) { + final RpslObject child = getResourceByKey(searchIp); + final RpslObject rpslObject = getResourceByKey((IpInterval) firstLessSpecific.getKey()); + if (child == null || rpslObject == null || isAdministrativeResource(child, rpslObject)) { LOGGER.debug("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return false; } return true; } - private static boolean isAdministrativeResource(final RpslObject children, final RpslObject rpslObject) { - final CIString childrenStatus = children.getValueForAttribute(AttributeType.STATUS); + private static boolean isAdministrativeResource(final RpslObject child, final RpslObject rpslObject) { + final CIString childStatus = child.getValueForAttribute(AttributeType.STATUS); final CIString statusAttributeValue = rpslObject.getValueForAttribute(AttributeType.STATUS); - return (rpslObject.getType().equals(ObjectType.INETNUM) && InetnumStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_UNSPECIFIED)) - || (rpslObject.getType().equals(ObjectType.INET6NUM) && Inet6numStatus.getStatusFor(childrenStatus).equals(ALLOCATED_BY_RIR) && - Inet6numStatus.getStatusFor(statusAttributeValue).equals(ALLOCATED_BY_RIR)); + return (rpslObject.getType() == ObjectType.INETNUM && InetnumStatus.getStatusFor(statusAttributeValue) == ALLOCATED_UNSPECIFIED) + || (rpslObject.getType() == ObjectType.INET6NUM) && + Inet6numStatus.getStatusFor(childStatus) == ALLOCATED_BY_RIR && Inet6numStatus.getStatusFor(statusAttributeValue) == ALLOCATED_BY_RIR; } @Nullable - private RpslObject getResourceByKey(final String key){ - if (IpInterval.parse(key) instanceof Ipv4Resource){ - return rpslObjectDao.getByKeyOrNull(ObjectType.INETNUM, key); + private RpslObject getResourceByKey(final IpInterval keyInterval){ + if (keyInterval instanceof Ipv4Resource){ + return rpslObjectDao.getByKeyOrNull(ObjectType.INETNUM, keyInterval.toString()); } - return rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, key); + return rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, keyInterval.toString()); } private IpTree getIpTree(final IpInterval searchIp) { diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 226c95107e..71ec855c7c 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -264,7 +264,7 @@ public Response relationSearch( //TODO: [MH] Status is being ignored until administrative resources are included in RDAP. If status is not // given or status is inactive...include administrative resources in the output. However, if status is active // return just non administrative resources, as we are doing now. - if (!StringUtil.isNullOrEmpty(status) && status.equalsIgnoreCase("inactive")) { + if ("inactive".equalsIgnoreCase(status)) { throw new RdapException("501 Not Implemented", "Inactive status is not implemented", HttpStatus.NOT_IMPLEMENTED_501); } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java index 21776674f5..48b5861038 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/RdapServiceTestIntegration.java @@ -3251,6 +3251,19 @@ public void get_top_then_less_specific_allocated_assigned_first_parent(){ public void get_ipv6_top_then_less_specific_allocated_assigned_first_parent(){ loadIpv6RelationTreeExample(); + databaseHelper.updateObject("" + + "inet6num: 2000::/3\n" + + "netname: TEST\n" + + "descr: The whole IPv6 address space\n" + + "country: NL\n" + + "tech-c: TP1-TEST\n" + + "admin-c: TP1-TEST\n" + + "status: ALLOCATED-BY-RIR\n" + + "mnt-by: OWNER-MNT\n" + + "created: 2022-08-14T11:48:28Z\n" + + "last-modified: 2022-10-25T12:22:39Z\n" + + "source: TEST"); + final SearchResult searchResult = createResource("ips/rirSearch1/top/2001:db8::/32") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); From ebc24f05a0a5fb167dd667f5cc66b9a8322c4ebb Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 16 Jan 2025 14:20:19 +0100 Subject: [PATCH 30/34] feat: stop when search ip is hit --- .../ripe/db/whois/api/rdap/RdapRelationService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index ca990c6599..623d821a1a 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; import java.util.List; +import java.util.Objects; import java.util.Set; import static net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus.ALLOCATED_BY_RIR; @@ -137,11 +138,13 @@ private IpEntry searchUpResource(final IpTree ipTree, final IpInterval searchIp) } private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval searchIp){ - final List lessAndExact = ipTree.findExactAndAllLessSpecific(searchIp); - final List less = ipTree.findAllLessSpecific(searchIp); + final List lessAndExact = ipTree.findExactAndAllLessSpecific(searchIp);; - for (int countLessSpecific = 0; countLessSpecific < less.size(); countLessSpecific++){ + for (int countLessSpecific = 0; countLessSpecific < lessAndExact.size(); countLessSpecific++){ final IpEntry ipEntry = lessAndExact.get(countLessSpecific); + if (Objects.equals(ipEntry.getKey().toString(), searchIp.toString())){ + break; + } final IpInterval childIpInterval = (IpInterval)lessAndExact.get(countLessSpecific+1).getKey(); if (existAndNoAdministrative(childIpInterval, ipEntry)){ return ipEntry; @@ -153,11 +156,11 @@ private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval sea private boolean existAndNoAdministrative(final IpInterval searchIp, final IpEntry firstLessSpecific){ final RpslObject child = getResourceByKey(searchIp); final RpslObject rpslObject = getResourceByKey((IpInterval) firstLessSpecific.getKey()); - if (child == null || rpslObject == null || isAdministrativeResource(child, rpslObject)) { + if (child == null || rpslObject == null) { LOGGER.debug("INET(6)NUM {} does not exist in RIPE Database ", firstLessSpecific.getKey().toString()); return false; } - return true; + return !isAdministrativeResource(child, rpslObject); } private static boolean isAdministrativeResource(final RpslObject child, final RpslObject rpslObject) { From 2c43e932fb9ef487c331b9419b1c66ee0d00eac2 Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 16 Jan 2025 14:35:49 +0100 Subject: [PATCH 31/34] feat: choose the size()-1 approach --- .../net/ripe/db/whois/api/rdap/RdapRelationService.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 623d821a1a..26940ed5ed 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -26,7 +26,6 @@ import javax.annotation.Nullable; import java.util.List; -import java.util.Objects; import java.util.Set; import static net.ripe.db.whois.common.rpsl.attrs.Inet6numStatus.ALLOCATED_BY_RIR; @@ -140,11 +139,9 @@ private IpEntry searchUpResource(final IpTree ipTree, final IpInterval searchIp) private IpEntry searchTopLevelResource(final IpTree ipTree, final IpInterval searchIp){ final List lessAndExact = ipTree.findExactAndAllLessSpecific(searchIp);; - for (int countLessSpecific = 0; countLessSpecific < lessAndExact.size(); countLessSpecific++){ + for (int countLessSpecific = 0; countLessSpecific < lessAndExact.size() - 1; countLessSpecific++){ final IpEntry ipEntry = lessAndExact.get(countLessSpecific); - if (Objects.equals(ipEntry.getKey().toString(), searchIp.toString())){ - break; - } + final IpInterval childIpInterval = (IpInterval)lessAndExact.get(countLessSpecific+1).getKey(); if (existAndNoAdministrative(childIpInterval, ipEntry)){ return ipEntry; From 193286a6c4a371ad6d8badb395b206723543c69a Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 16 Jan 2025 14:39:46 +0100 Subject: [PATCH 32/34] feat: remove static --- .../net/ripe/db/whois/api/rdap/RdapRelationService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 26940ed5ed..21d2d02d72 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -119,12 +119,12 @@ private void extractBottomMatches(final IpTree ipTree, final IpInterval searchIp } } - private static boolean childrenCoverParentRange(final IpInterval firstResource, final IpInterval lastResource, final IpInterval parent){ + private boolean childrenCoverParentRange(final IpInterval firstResource, final IpInterval lastResource, final IpInterval parent){ return firstResource.beginAsInetAddress().equals(parent.beginAsInetAddress()) && lastResource.endAsInetAddress().equals(parent.endAsInetAddress()); } - private static List findSiblingsAndExact(final IpTree ipTree, final List parent) { + private List findSiblingsAndExact(final IpTree ipTree, final List parent) { return ipTree.findFirstMoreSpecific(IpInterval.parse(parent.getFirst().getKey().toString())); } @@ -160,7 +160,7 @@ private boolean existAndNoAdministrative(final IpInterval searchIp, final IpEntr return !isAdministrativeResource(child, rpslObject); } - private static boolean isAdministrativeResource(final RpslObject child, final RpslObject rpslObject) { + private boolean isAdministrativeResource(final RpslObject child, final RpslObject rpslObject) { final CIString childStatus = child.getValueForAttribute(AttributeType.STATUS); final CIString statusAttributeValue = rpslObject.getValueForAttribute(AttributeType.STATUS); return (rpslObject.getType() == ObjectType.INETNUM && InetnumStatus.getStatusFor(statusAttributeValue) == ALLOCATED_UNSPECIFIED) From f9338f63691e88910fb73153bf2c61cc0deee660 Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 16 Jan 2025 14:55:42 +0100 Subject: [PATCH 33/34] feat: refactor --- .../db/whois/api/rdap/RdapRelationService.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 21d2d02d72..4e352d57a2 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -171,23 +171,14 @@ private boolean isAdministrativeResource(final RpslObject child, final RpslObjec @Nullable private RpslObject getResourceByKey(final IpInterval keyInterval){ - if (keyInterval instanceof Ipv4Resource){ - return rpslObjectDao.getByKeyOrNull(ObjectType.INETNUM, keyInterval.toString()); - } - return rpslObjectDao.getByKeyOrNull(ObjectType.INET6NUM, keyInterval.toString()); + return rpslObjectDao.getByKeyOrNull(keyInterval instanceof Ipv4Resource ? ObjectType.INETNUM : ObjectType.INET6NUM, keyInterval.toString()); } private IpTree getIpTree(final IpInterval searchIp) { - if (searchIp instanceof Ipv4Resource) { - return ip4Tree; - } - return ip6Tree; + return searchIp instanceof Ipv4Resource ? ip4Tree : ip6Tree; } private IpTree getIpDomainTree(final IpInterval reverseIp) { - if (reverseIp instanceof Ipv4Resource) { - return ipv4DomainTree; - } - return ipv6DomainTree; + return reverseIp instanceof Ipv4Resource ? ipv4DomainTree : ipv6DomainTree; } } From b60dea6ec38d449ccbdc868da9b09cfe62267fdf Mon Sep 17 00:00:00 2001 From: mherran Date: Fri, 17 Jan 2025 10:57:23 +0100 Subject: [PATCH 34/34] feat: refactor --- .../java/net/ripe/db/whois/api/rdap/RdapRelationService.java | 4 ++-- .../src/main/java/net/ripe/db/whois/api/rdap/RdapService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java index 4e352d57a2..08c597e1b6 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapRelationService.java @@ -54,7 +54,7 @@ public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, this.rpslObjectDao = rpslObjectDao; } - public List getDomainRelationPkeys(final String pkey, final RelationType relationType){ + public List getDomainsByRelationType(final String pkey, final RelationType relationType){ final Domain domain = Domain.parse(pkey); final IpInterval reverseIp = domain.getReverseIp(); final List domainEntries = getEntries(getIpDomainTree(reverseIp), relationType, reverseIp); @@ -87,7 +87,7 @@ private List getEntries(final IpTree ipTree, final RelationType relatio private List searchBottomResources(final IpTree ipTree, final IpInterval searchIp){ final List mostSpecificValues = ipTree.findMostSpecific(searchIp); - final Set mostSpecificFillingOverlaps = Sets.newConcurrentHashSet(); + final Set mostSpecificFillingOverlaps = Sets.newHashSet(); mostSpecificValues.forEach(ipResource -> extractBottomMatches(ipTree, searchIp, ipResource, mostSpecificFillingOverlaps)); return mostSpecificFillingOverlaps.stream().toList(); } diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java index 71ec855c7c..85fbd38abd 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rdap/RdapService.java @@ -536,7 +536,7 @@ private List handleRelationQuery(final HttpServletRequest request, f case AUTNUMS -> throw new RdapException("501 Not Implemented", "Relation queries not allowed for autnum", HttpStatus.NOT_IMPLEMENTED_501); case DOMAINS -> { rdapRequestValidator.validateDomain(key); - final List relatedPkeys = rdapRelationService.getDomainRelationPkeys(key, relationType); + final List relatedPkeys = rdapRelationService.getDomainsByRelationType(key, relationType); rpslObjects = relatedPkeys .stream()