From 0116abdb8f82cb684b578f7a6449c015215357a7 Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 28 Nov 2024 15:46:36 +0100 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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 cca2e7fa9206361683452069bdb1ebda8a77fcbe Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 12 Dec 2024 13:02:29 +0100 Subject: [PATCH 11/13] feat: Add it --- .../db/whois/api/rdap/RdapObjectMapper.java | 17 +++- .../whois/api/rdap/RdapRelationService.java | 32 ++++++- .../ripe/db/whois/api/rdap/RdapService.java | 1 - .../api/rdap/AbstractRdapIntegrationTest.java | 2 +- .../api/rdap/RdapServiceTestIntegration.java | 95 ++++++++++++++++++- 5 files changed, 137 insertions(+), 10 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 1743319603..5d9c28f9b1 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 @@ -22,6 +22,7 @@ import net.ripe.db.whois.api.rdap.domain.Nameserver; import net.ripe.db.whois.api.rdap.domain.Notice; import net.ripe.db.whois.api.rdap.domain.RdapObject; +import net.ripe.db.whois.api.rdap.domain.RelationType; 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; @@ -118,6 +119,7 @@ class RdapObjectMapper { private final Ipv4Tree ipv4Tree; private final Ipv6Tree ipv6Tree; private final String port43; + private final RdapRelationService rdapRelationService; private static final Map CONTACT_ATTRIBUTE_TO_ROLE_NAME = Map.of( ADMIN_C, Role.ADMINISTRATIVE, TECH_C, Role.TECHNICAL, @@ -133,13 +135,15 @@ public RdapObjectMapper( final ReservedResources reservedResources, final Ipv4Tree ipv4Tree, final Ipv6Tree ipv6Tree, - @Value("${rdap.port43:}") final String port43) { + @Value("${rdap.port43:}") final String port43, + final RdapRelationService rdapRelationService) { this.noticeFactory = noticeFactory; this.rpslObjectDao = rpslObjectDao; this.ipv4Tree = ipv4Tree; this.ipv6Tree = ipv6Tree; this.port43 = port43; this.reservedResources = reservedResources; + this.rdapRelationService = rdapRelationService; } public Object map(final String requestUrl, @@ -155,16 +159,19 @@ public Object mapSearch(final String requestUrl, final List objects, case DOMAIN -> { final Domain domain = (Domain) getRdapObject(requestUrl, object, null); mapRedactions(domain); + rdapRelationService.mapRelationLinks(domain, requestUrl); searchResult.addDomainSearchResult(domain); } case INET6NUM, INETNUM -> { final Ip ip = (Ip) getRdapObject(requestUrl, object, null); mapRedactions(ip); + rdapRelationService.mapRelationLinks(ip, requestUrl); searchResult.addIpSearchResult(ip); } case AUT_NUM -> { final Autnum autnum = (Autnum) getRdapObject(requestUrl, object, null); mapRedactions(autnum); + rdapRelationService.mapRelationLinks(autnum, requestUrl); searchResult.addAutnumSearchResult(autnum); } default -> { @@ -322,10 +329,12 @@ private RdapObject getRdapObject(final String requestUrl, final RpslObject rpslO private RdapObject mapCommons(final RdapObject rdapResponse, final String requestUrl) { final RdapObject rdapObject = mapCommonNoticesAndPort(rdapResponse, requestUrl); mapCommonLinks(rdapObject, requestUrl); + rdapRelationService.mapRelationLinks(rdapResponse, requestUrl); mapRedactions(rdapResponse); return mapCommonConformances(rdapObject); } + private void mapCommonLinks(final RdapObject rdapResponse, final String requestUrl) { if (requestUrl != null) { rdapResponse.getLinks().add(new Link(requestUrl, "self", requestUrl, null, null, null)); @@ -471,11 +480,11 @@ private IpEntry lookupParentIpEntry(final IpInterval ipInterval) { private static Remark createRemark(final RpslObject rpslObject) { final List descriptions = Lists.newArrayList(); - for (final CIString description : rpslObject.getValuesForAttribute(AttributeType.DESCR)) { + for (final CIString description : rpslObject.getValuesForAttribute(DESCR)) { descriptions.add(description.toString()); } - for (final CIString remark : rpslObject.getValuesForAttribute(AttributeType.REMARKS)) { + for (final CIString remark : rpslObject.getValuesForAttribute(REMARKS)) { descriptions.add(remark.toString()); } @@ -489,7 +498,7 @@ private static Remark createRemark(final CIString key, final AbuseContact abuseC } private static boolean hasDescriptionsOrRemarks(final RpslObject rpslObject) { - return !rpslObject.getValuesForAttribute(AttributeType.DESCR).isEmpty() || !rpslObject.getValuesForAttribute(AttributeType.REMARKS).isEmpty(); + return !rpslObject.getValuesForAttribute(DESCR).isEmpty() || !rpslObject.getValuesForAttribute(REMARKS).isEmpty(); } private static Event createEvent(final LocalDateTime lastChanged, final Action action) { 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..aac786a47a 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,10 @@ package net.ripe.db.whois.api.rdap; import com.google.common.collect.Sets; +import net.ripe.db.whois.api.rdap.domain.Autnum; +import net.ripe.db.whois.api.rdap.domain.Ip; +import net.ripe.db.whois.api.rdap.domain.Link; +import net.ripe.db.whois.api.rdap.domain.RdapObject; 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; @@ -41,15 +45,18 @@ public class RdapRelationService { private final Ipv4DomainTree ipv4DomainTree; private final Ipv6DomainTree ipv6DomainTree; private final RpslObjectDao rpslObjectDao; + private final String rdapRirSearchSkeleton; public RdapRelationService(final Ipv4Tree ip4Tree, final Ipv6Tree ip6Tree, final Ipv4DomainTree ipv4DomainTree, final Ipv6DomainTree ipv6DomainTree, - final RpslObjectDao rpslObjectDao) { + final RpslObjectDao rpslObjectDao, + @Value("${rdap.public.baseUrl:}") final String baseUrl) { this.ip4Tree = ip4Tree; this.ip6Tree = ip6Tree; this.ipv4DomainTree = ipv4DomainTree; this.ipv6DomainTree = ipv6DomainTree; this.rpslObjectDao = rpslObjectDao; + this.rdapRirSearchSkeleton = baseUrl + "/%s/rirSearch1/%s/%s"; } public List getDomainRelationPkeys(final String pkey, final RelationType relationType, final String status){ @@ -69,6 +76,29 @@ public List getInetnumRelationPkeys(final String pkey, final RelationTyp return ipEntries.stream().map(ipEntry -> ipEntry.getKey().toString()).toList(); } + public void mapRelationLinks(final RdapObject rdapResponse, final String requestUrl){ + if (requestUrl == null || requestUrl.contains("rirSearch1")){ + return; + } + switch (rdapResponse){ + case Ip ip -> mapCommonRelationLinks(rdapResponse, requestUrl, "ips", ip.getHandle()); + case net.ripe.db.whois.api.rdap.domain.Domain domain -> mapCommonRelationLinks(rdapResponse, requestUrl, "domains", domain.getHandle()); + case Autnum autnum -> mapCommonRelationLinks(rdapResponse, requestUrl, "autnums", autnum.getHandle()); + default -> {} + } + } + + private void mapCommonRelationLinks(final RdapObject rdapResponse, final String requestUrl, final String objectType, final String handle){ + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.UP.getValue(), String.format(rdapRirSearchSkeleton, + objectType, RelationType.UP.getValue(), handle), "application/rdap+json", null, null)); + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.DOWN.getValue(), String.format(rdapRirSearchSkeleton, + objectType, RelationType.DOWN.getValue(), handle), "application/rdap+json", null, null)); + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.TOP.getValue(), String.format(rdapRirSearchSkeleton, + objectType, RelationType.TOP.getValue(), handle), "application/rdap+json", null, null)); + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.BOTTOM.getValue(), String.format(rdapRirSearchSkeleton, + objectType, RelationType.BOTTOM.getValue(), handle), "application/rdap+json", null, null)); + } + private List getIpEntries(final IpTree ipTree, final RelationType relationType, final IpInterval reverseIp, final String status) { return switch (relationType) { 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..63964740b5 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,7 +5,6 @@ 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; diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java index e9c439f750..f220218d5a 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java @@ -50,7 +50,7 @@ protected WebTarget createResource(final String path) { } protected String syncupdate(String data) { - WebTarget resource = RestTest.target(getPort(), String.format("whois/syncupdates/test")); + WebTarget resource = RestTest.target(getPort(), "whois/syncupdates/test"); return resource.request() .post(jakarta.ws.rs.client.Entity.entity("DATA=" + RestClientUtils.encode(data), MediaType.APPLICATION_FORM_URLENCODED), 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..800768bbd9 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 @@ -22,6 +22,7 @@ import net.ripe.db.whois.api.rdap.domain.Notice; import net.ripe.db.whois.api.rdap.domain.RdapObject; import net.ripe.db.whois.api.rdap.domain.Redaction; +import net.ripe.db.whois.api.rdap.domain.RelationType; 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; @@ -33,12 +34,16 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.test.annotation.DirtiesContext; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import static net.ripe.db.whois.common.rpsl.AttributeType.COUNTRY; import static net.ripe.db.whois.common.rpsl.AttributeType.LANGUAGE; @@ -65,6 +70,9 @@ public class RdapServiceTestIntegration extends AbstractRdapIntegrationTest { @Autowired TestWhoisLog queryLog; + @Value("${rdap.public.baseUrl:}") + private String rdapBaseUrl; + @BeforeEach public void setup() { databaseHelper.addObject("" + @@ -250,7 +258,7 @@ public void lookup_inetnum_range_reserved() { assertThat(notices.get(1).getDescription(), contains("Objects returned came from source", "TEST")); assertThat(notices.get(1).getLinks(), hasSize(0)); - assertTnCNotice(notices.get(2), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); + assertTnCNotice(notices.get(6), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); assertCopyrightLink(ip.getLinks(), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); } @@ -3655,7 +3663,7 @@ public void get_down_when_no_child_then_empty_response(){ public void down_with_status_then_501(){ final ServerErrorException notImplementedException = assertThrows(ServerErrorException.class, () -> { - createResource("ip/rirSearch1/down/192.0.2.0/24?status=inactive") + createResource("ips/rirSearch1/down/192.0.2.0/24?status=inactive") .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3664,8 +3672,89 @@ public void down_with_status_then_501(){ assertErrorDescription(notImplementedException, "Status is not implement in down and bottom relation"); } + // Links + + @Test + public void use_relation_links_then_up_bottom_top_down(){ + loadIpv4RelationTreeExample(); + final Ip ip = createResource("ip/192.0.2.0/24") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(Ip.class); + + final Map relationCalls = getRelationCallsFromLinks(ip.getLinks()); + + assertThat(relationCalls.size(), is(4)); + + //TOP + // TODO: We do not support administrative resources, we return 404. Change this when we support them + NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { + createResource(relationCalls.get(RelationType.TOP)) + .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 + final SearchResult bottomSearchResult = createResource(relationCalls.get(RelationType.BOTTOM)) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List bottomIpResults = bottomSearchResult.getIpSearchResults(); + assertThat(bottomIpResults.size(), is(5)); + assertThat(bottomIpResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.0")); //32 + assertThat(bottomIpResults.get(1).getHandle(), is("192.0.2.0 - 192.0.2.15")); //28 + assertThat(bottomIpResults.get(2).getHandle(), is("192.0.2.0 - 192.0.2.127")); //25 + assertThat(bottomIpResults.get(3).getHandle(), is("192.0.2.128 - 192.0.2.191")); //26 + assertThat(bottomIpResults.get(4).getHandle(), is("192.0.2.192 - 192.0.2.255")); //26 + + //DOWN + final SearchResult downSearchResult = createResource(relationCalls.get(RelationType.DOWN)) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List downIpResults = downSearchResult.getIpSearchResults(); + assertThat(downIpResults.size(), is(2)); + assertThat(downIpResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); //25 + assertThat(downIpResults.get(1).getHandle(), is("192.0.2.128 - 192.0.2.255")); //25 + + //UP + // TODO: We do not support administrative resources, we return 404. Change this when we support them + notFoundException = assertThrows(NotFoundException.class, () -> { + createResource(relationCalls.get(RelationType.UP)) + .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"); + + } + /* Helper methods*/ + private Map getRelationCallsFromLinks(final List links){ + return links.stream() + .filter(link -> { + if (link.getRel() == null){ + return false; + } + try { + RelationType.valueOf(link.getRel().toUpperCase()); + return true; + } catch (IllegalArgumentException ex) { + return false; + } + }) + .collect(Collectors.toMap( + link -> RelationType.valueOf(link.getRel().toUpperCase()), + link -> link.getHref().replace(rdapBaseUrl + "/", "") + )); + } + private void assertCommon(RdapObject object) { assertThat(object.getPort43(), is("whois.ripe.net")); assertThat(object.getRdapConformance(), hasSize(4)); @@ -3673,7 +3762,7 @@ private void assertCommon(RdapObject object) { } private void assertGeoFeedLink(final List links, final String value) { - assertThat(links, hasSize(3)); + assertThat(links, hasSize(7)); final Optional geoFeedLink = links.stream().filter(link -> link.getRel().equals("geo")).findFirst(); assertThat(geoFeedLink.isPresent(), is(true)); From f142244e5df5ed25f378ccc5652585c79175422e Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 12 Dec 2024 15:43:19 +0100 Subject: [PATCH 12/13] feat: add extra links and fix ITs --- .../whois/api/rdap/RdapRelationService.java | 9 +++ .../db/whois/api/AbstractIntegrationTest.java | 27 +++++++++ .../RdapElasticServiceTestIntegration.java | 15 +++++ .../api/rdap/AbstractRdapIntegrationTest.java | 2 +- .../api/rdap/RdapServiceTestIntegration.java | 59 ++++++++----------- 5 files changed, 75 insertions(+), 37 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 aac786a47a..df83de931e 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,10 +91,19 @@ public void mapRelationLinks(final RdapObject rdapResponse, final String request private void mapCommonRelationLinks(final RdapObject rdapResponse, final String requestUrl, final String objectType, final String handle){ rdapResponse.getLinks().add(new Link(requestUrl, RelationType.UP.getValue(), String.format(rdapRirSearchSkeleton, objectType, RelationType.UP.getValue(), handle), "application/rdap+json", null, null)); + + rdapResponse.getLinks().add(new Link(requestUrl, "up-active", String.format(rdapRirSearchSkeleton + "?status=active", + objectType, RelationType.UP.getValue(), handle), "application/rdap+json", null, null)); + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.DOWN.getValue(), String.format(rdapRirSearchSkeleton, objectType, RelationType.DOWN.getValue(), handle), "application/rdap+json", null, null)); + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.TOP.getValue(), String.format(rdapRirSearchSkeleton, objectType, RelationType.TOP.getValue(), handle), "application/rdap+json", null, null)); + + rdapResponse.getLinks().add(new Link(requestUrl, "top-active", String.format(rdapRirSearchSkeleton + "?status=active", + objectType, RelationType.TOP.getValue(), handle), "application/rdap+json", null, null)); + rdapResponse.getLinks().add(new Link(requestUrl, RelationType.BOTTOM.getValue(), String.format(rdapRirSearchSkeleton, objectType, RelationType.BOTTOM.getValue(), handle), "application/rdap+json", null, null)); } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/AbstractIntegrationTest.java b/whois-api/src/test/java/net/ripe/db/whois/api/AbstractIntegrationTest.java index 77c1efac9b..c123d6cdf4 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/AbstractIntegrationTest.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/AbstractIntegrationTest.java @@ -2,6 +2,8 @@ import com.google.common.util.concurrent.Uninterruptibles; import net.ripe.db.whois.api.httpserver.JettyBootstrap; +import net.ripe.db.whois.api.rdap.domain.Link; +import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.common.ApplicationService; import net.ripe.db.whois.common.support.AbstractDaoIntegrationTest; import org.apache.logging.log4j.Level; @@ -15,6 +17,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.ContextConfiguration; import java.io.StringWriter; @@ -22,7 +25,9 @@ import java.time.Instant; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @ContextConfiguration(locations = {"classpath:applicationContext-api-test.xml"}) public abstract class AbstractIntegrationTest extends AbstractDaoIntegrationTest { @@ -31,6 +36,9 @@ public abstract class AbstractIntegrationTest extends AbstractDaoIntegrationTest protected final StringWriter stringWriter = new StringWriter(); + @Value("${rdap.public.baseUrl:}") + private String rdapBaseUrl; + @BeforeEach public void startServer() { for (final ApplicationService applicationService : applicationServices) { @@ -96,4 +104,23 @@ protected void removeLog4jAppender() { protected String getRequestLog() { return stringWriter.toString(); } + + protected Map getRelationCallsFromLinks(final List links){ + return links.stream() + .filter(link -> { + if (link.getRel() == null){ + return false; + } + try { + RelationType.valueOf(link.getRel().toUpperCase()); + return true; + } catch (IllegalArgumentException ex) { + return link.getRel().equals("up-active") || link.getRel().equals("top-active"); + } + }) + .collect(Collectors.toMap( + link -> link.getRel().toUpperCase(), + link -> link.getHref().replace(rdapBaseUrl + "/", "") + )); + } } diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/elasticsearch/RdapElasticServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/elasticsearch/RdapElasticServiceTestIntegration.java index 518f545fa5..71d1472af8 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/elasticsearch/RdapElasticServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/elasticsearch/RdapElasticServiceTestIntegration.java @@ -19,6 +19,7 @@ import net.ripe.db.whois.api.rdap.domain.Notice; import net.ripe.db.whois.api.rdap.domain.RdapObject; import net.ripe.db.whois.api.rdap.domain.Redaction; +import net.ripe.db.whois.api.rdap.domain.RelationType; import net.ripe.db.whois.api.rdap.domain.SearchResult; import net.ripe.db.whois.api.rest.client.RestClientUtils; import net.ripe.db.whois.common.rpsl.RpslObject; @@ -40,6 +41,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -880,6 +882,19 @@ public void search_redactions() { assertThat(result.getRdapConformance(), containsInAnyOrder("cidr0", "rdap_level_0", "nro_rdap_profile_0", "redacted")); } + // Relation links + @Test + public void search_domain_then_domain_relations(){ + final SearchResult response = createResource("domains?name=31.12.202.in-addr.arpa") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + assertThat(response.getDomainSearchResults().getFirst().getHandle(), equalTo("31.12.202.in-addr.arpa")); + final Map relationLinks = getRelationCallsFromLinks(response.getDomainSearchResults().getFirst().getLinks()); + assertThat(relationLinks.size(), is(6)); + } + + // helper methods protected WebTarget createResource(final String path) { diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java index f220218d5a..e9c439f750 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rdap/AbstractRdapIntegrationTest.java @@ -50,7 +50,7 @@ protected WebTarget createResource(final String path) { } protected String syncupdate(String data) { - WebTarget resource = RestTest.target(getPort(), "whois/syncupdates/test"); + WebTarget resource = RestTest.target(getPort(), String.format("whois/syncupdates/test")); return resource.request() .post(jakarta.ws.rs.client.Entity.entity("DATA=" + RestClientUtils.encode(data), MediaType.APPLICATION_FORM_URLENCODED), 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 800768bbd9..984443bbb1 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 @@ -34,14 +34,13 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.test.annotation.DirtiesContext; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -70,9 +69,6 @@ public class RdapServiceTestIntegration extends AbstractRdapIntegrationTest { @Autowired TestWhoisLog queryLog; - @Value("${rdap.public.baseUrl:}") - private String rdapBaseUrl; - @BeforeEach public void setup() { databaseHelper.addObject("" + @@ -258,8 +254,8 @@ public void lookup_inetnum_range_reserved() { assertThat(notices.get(1).getDescription(), contains("Objects returned came from source", "TEST")); assertThat(notices.get(1).getLinks(), hasSize(0)); - assertTnCNotice(notices.get(6), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); - assertCopyrightLink(ip.getLinks(), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); + assertTnCNotice(notices.get(2), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); + assertResourceCopyrightLink(ip.getLinks(), "https://rdap.db.ripe.net/ip/192.0.2.0/24"); } @Test @@ -868,7 +864,7 @@ public void lookup_inet6num_with_prefix_length() { assertThat(notices.get(1).getTitle(), is("Source")); assertTnCNotice(notices.get(2), "https://rdap.db.ripe.net/ip/2001:2002:2003::/48"); - assertCopyrightLink(ip.getLinks(), "https://rdap.db.ripe.net/ip/2001:2002:2003::/48"); + assertResourceCopyrightLink(ip.getLinks(), "https://rdap.db.ripe.net/ip/2001:2002:2003::/48"); } @Test @@ -1657,7 +1653,7 @@ public void lookup_single_autnum() { assertThat(entities.get(1).getHandle(), is("TP1-TEST")); assertThat(entities.get(1).getRoles(), containsInAnyOrder(Role.ADMINISTRATIVE, Role.TECHNICAL)); - assertCopyrightLink(autnum.getLinks(), "https://rdap.db.ripe.net/autnum/102"); + assertResourceCopyrightLink(autnum.getLinks(), "https://rdap.db.ripe.net/autnum/102"); final List notices = autnum.getNotices(); assertThat(notices, hasSize(4)); @@ -3681,14 +3677,14 @@ public void use_relation_links_then_up_bottom_top_down(){ .request(MediaType.APPLICATION_JSON_TYPE) .get(Ip.class); - final Map relationCalls = getRelationCallsFromLinks(ip.getLinks()); + final Map relationCalls = getRelationCallsFromLinks(ip.getLinks()); - assertThat(relationCalls.size(), is(4)); + assertThat(relationCalls.size(), is(6)); //TOP // TODO: We do not support administrative resources, we return 404. Change this when we support them NotFoundException notFoundException = assertThrows(NotFoundException.class, () -> { - createResource(relationCalls.get(RelationType.TOP)) + createResource(relationCalls.get(RelationType.TOP.name())) .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3698,7 +3694,7 @@ public void use_relation_links_then_up_bottom_top_down(){ assertErrorDescription(notFoundException, "No top level object has been found for 192.0.2.0/24"); //BOTTOM - final SearchResult bottomSearchResult = createResource(relationCalls.get(RelationType.BOTTOM)) + final SearchResult bottomSearchResult = createResource(relationCalls.get(RelationType.BOTTOM.name())) .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3711,7 +3707,7 @@ public void use_relation_links_then_up_bottom_top_down(){ assertThat(bottomIpResults.get(4).getHandle(), is("192.0.2.192 - 192.0.2.255")); //26 //DOWN - final SearchResult downSearchResult = createResource(relationCalls.get(RelationType.DOWN)) + final SearchResult downSearchResult = createResource(relationCalls.get(RelationType.DOWN.name())) .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); @@ -3723,7 +3719,7 @@ public void use_relation_links_then_up_bottom_top_down(){ //UP // TODO: We do not support administrative resources, we return 404. Change this when we support them notFoundException = assertThrows(NotFoundException.class, () -> { - createResource(relationCalls.get(RelationType.UP)) + createResource(relationCalls.get(RelationType.UP.name())) .request(MediaType.APPLICATION_JSON_TYPE) .get(SearchResult.class); }); @@ -3736,25 +3732,6 @@ public void use_relation_links_then_up_bottom_top_down(){ /* Helper methods*/ - private Map getRelationCallsFromLinks(final List links){ - return links.stream() - .filter(link -> { - if (link.getRel() == null){ - return false; - } - try { - RelationType.valueOf(link.getRel().toUpperCase()); - return true; - } catch (IllegalArgumentException ex) { - return false; - } - }) - .collect(Collectors.toMap( - link -> RelationType.valueOf(link.getRel().toUpperCase()), - link -> link.getHref().replace(rdapBaseUrl + "/", "") - )); - } - private void assertCommon(RdapObject object) { assertThat(object.getPort43(), is("whois.ripe.net")); assertThat(object.getRdapConformance(), hasSize(4)); @@ -3762,7 +3739,7 @@ private void assertCommon(RdapObject object) { } private void assertGeoFeedLink(final List links, final String value) { - assertThat(links, hasSize(7)); + assertThat(links, hasSize(9)); final Optional geoFeedLink = links.stream().filter(link -> link.getRel().equals("geo")).findFirst(); assertThat(geoFeedLink.isPresent(), is(true)); @@ -3771,8 +3748,18 @@ private void assertGeoFeedLink(final List links, final String value) { assertThat(geoFeedLink.get().getType(), is("application/geofeed+csv")); } + private void assertResourceCopyrightLink(final List links, final String value) { + assertThat(links.size(), is(8)); + + final List copyrightLinks = links.stream() + .filter(link -> link.getRel().equals("copyright") || link.getRel().equals("self")) + .collect(Collectors.toCollection(ArrayList::new)); + + assertCopyrightLink(copyrightLinks, value); + } + private void assertCopyrightLink(final List links, final String value) { - assertThat(links, hasSize(2)); + assertThat(links.size(), is(2)); Collections.sort(links); assertThat(links.get(0).getRel(), is("copyright")); From 1ab06bfd605af5253c540d1058d81c99c644a1e2 Mon Sep 17 00:00:00 2001 From: mherran Date: Thu, 12 Dec 2024 16:08:45 +0100 Subject: [PATCH 13/13] feat: Add ITs --- .../api/rdap/RdapServiceTestIntegration.java | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 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 984443bbb1..c8c552800a 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 @@ -3673,7 +3673,23 @@ public void down_with_status_then_501(){ @Test public void use_relation_links_then_up_bottom_top_down(){ loadIpv4RelationTreeExample(); - final Ip ip = createResource("ip/192.0.2.0/24") + + 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"); + + ipTreeUpdater.rebuild(); + + final Ip ip = createResource("ip/192.0.2.0/25") .request(MediaType.APPLICATION_JSON_TYPE) .get(Ip.class); @@ -3691,7 +3707,18 @@ public void use_relation_links_then_up_bottom_top_down(){ 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/25"); + + + //TOP active + final SearchResult topSearchResult = createResource(relationCalls.get("TOP-ACTIVE")) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List topIpResults = topSearchResult.getIpSearchResults(); + assertThat(topIpResults.size(), is(1)); + assertThat(topIpResults.getFirst().getHandle(), is("192.0.0.0 - 192.0.255.255")); //16 + //BOTTOM final SearchResult bottomSearchResult = createResource(relationCalls.get(RelationType.BOTTOM.name())) @@ -3699,12 +3726,9 @@ public void use_relation_links_then_up_bottom_top_down(){ .get(SearchResult.class); final List bottomIpResults = bottomSearchResult.getIpSearchResults(); - assertThat(bottomIpResults.size(), is(5)); + assertThat(bottomIpResults.size(), is(2)); assertThat(bottomIpResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.0")); //32 assertThat(bottomIpResults.get(1).getHandle(), is("192.0.2.0 - 192.0.2.15")); //28 - assertThat(bottomIpResults.get(2).getHandle(), is("192.0.2.0 - 192.0.2.127")); //25 - assertThat(bottomIpResults.get(3).getHandle(), is("192.0.2.128 - 192.0.2.191")); //26 - assertThat(bottomIpResults.get(4).getHandle(), is("192.0.2.192 - 192.0.2.255")); //26 //DOWN final SearchResult downSearchResult = createResource(relationCalls.get(RelationType.DOWN.name())) @@ -3712,9 +3736,8 @@ public void use_relation_links_then_up_bottom_top_down(){ .get(SearchResult.class); final List downIpResults = downSearchResult.getIpSearchResults(); - assertThat(downIpResults.size(), is(2)); - assertThat(downIpResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.127")); //25 - assertThat(downIpResults.get(1).getHandle(), is("192.0.2.128 - 192.0.2.255")); //25 + assertThat(downIpResults.size(), is(1)); + assertThat(bottomIpResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.0")); //32 //UP // TODO: We do not support administrative resources, we return 404. Change this when we support them @@ -3726,10 +3749,19 @@ public void use_relation_links_then_up_bottom_top_down(){ 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.2.0/25"); + //UP active + final SearchResult upSearchResult = createResource(relationCalls.get("UP-ACTIVE")) + .request(MediaType.APPLICATION_JSON_TYPE) + .get(SearchResult.class); + + final List upIpResults = upSearchResult.getIpSearchResults(); + assertThat(upIpResults.size(), is(1)); + assertThat(upIpResults.getFirst().getHandle(), is("192.0.2.0 - 192.0.2.255")); //24 } + /* Helper methods*/ private void assertCommon(RdapObject object) {