diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/mail/dequeue/MessageDequeue.java b/whois-api/src/main/java/net/ripe/db/whois/api/mail/dequeue/MessageDequeue.java index 681dde41a9..6e8c0dbf71 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/mail/dequeue/MessageDequeue.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/mail/dequeue/MessageDequeue.java @@ -240,7 +240,7 @@ String getMessageIdLocalPart(final Message message) throws MessagingException { return "No-Message-Id." + dateTimeProvider.getElapsedTime(); } - private void handleMessageInContext(final String messageId, final MimeMessage message) throws MessagingException, IOException { + private void handleMessageInContext(final String messageId, final MimeMessage message) throws MessagingException { loggerContext.log("msg-in.txt", new MailMessageLogCallback(message)); mailMessageDao.setStatus(messageId, DequeueStatus.LOGGED); diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rest/RpslObjectStreamer.java b/whois-api/src/main/java/net/ripe/db/whois/api/rest/RpslObjectStreamer.java index 9007c27c97..4cef2cdf79 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rest/RpslObjectStreamer.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rest/RpslObjectStreamer.java @@ -1,6 +1,10 @@ package net.ripe.db.whois.api.rest; import com.google.common.collect.Lists; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; import net.ripe.db.whois.api.rest.client.StreamingException; import net.ripe.db.whois.api.rest.domain.Link; import net.ripe.db.whois.api.rest.domain.Parameters; @@ -29,10 +33,6 @@ import org.springframework.stereotype.Component; import javax.annotation.Nullable; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.StreamingOutput; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; @@ -193,6 +193,7 @@ private void streamObject(@Nullable final RpslObject rpslObject) { whoisObjectServerMapper.mapAbuseContact(whoisObject, parameters, rpslObject); whoisObjectServerMapper.mapManagedAttributes(whoisObject, parameters, rpslObject); whoisObjectServerMapper.mapResourceHolder(whoisObject, parameters, rpslObject); + whoisObjectServerMapper.mapObjectMessages(whoisObject, parameters, rpslObject); if (streamingMarshal instanceof StreamingMarshalTextPlain) { streamingMarshal.writeArray(rpslObject); diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rest/WhoisSearchService.java b/whois-api/src/main/java/net/ripe/db/whois/api/rest/WhoisSearchService.java index b581f037a0..f6940134e3 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rest/WhoisSearchService.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rest/WhoisSearchService.java @@ -4,6 +4,7 @@ import com.google.common.collect.Sets; import com.google.common.net.InetAddresses; import jakarta.ws.rs.CookieParam; +import jakarta.ws.rs.DefaultValue; import net.ripe.db.whois.api.QueryBuilder; import net.ripe.db.whois.api.rest.domain.Flags; import net.ripe.db.whois.api.rest.domain.InverseAttributes; @@ -136,7 +137,8 @@ public Response search( @QueryParam("resource-holder") final String resourceHolder, @QueryParam("abuse-contact") final String abuseContact, @QueryParam("limit") final Integer limit, - @QueryParam("offset") final Integer offset) { + @QueryParam("offset") final Integer offset, + @QueryParam("roa-check") @DefaultValue("false") final Boolean roaCheck) { validateSources(request, sources); validateSearchKey(request, searchKey); @@ -169,6 +171,7 @@ public Response search( .managedAttributes(isQueryParamSet(managedAttributes)) .resourceHolder(isQueryParamSet(resourceHolder)) .abuseContact(isQueryParamSet(abuseContact)) + .roaCheck(roaCheck) .limit(limit) .offset(offset) .unformatted(isQueryParamSet(unformatted)) diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapper.java b/whois-api/src/main/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapper.java index 5abc8761f4..669e7383cd 100644 --- a/whois-api/src/main/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapper.java +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapper.java @@ -2,15 +2,17 @@ import com.google.common.collect.Lists; import net.ripe.db.whois.api.rest.domain.Attribute; +import net.ripe.db.whois.api.rest.domain.ObjectMessages; import net.ripe.db.whois.api.rest.domain.Parameters; import net.ripe.db.whois.api.rest.domain.WhoisObject; import net.ripe.db.whois.api.rest.domain.WhoisVersion; import net.ripe.db.whois.api.rest.search.AbuseContactSearch; -import net.ripe.db.whois.common.search.ManagedAttributeSearch; +import net.ripe.db.whois.api.rest.search.RpslMessageGenerator; import net.ripe.db.whois.api.rest.search.ResourceHolderSearch; import net.ripe.db.whois.common.domain.serials.Operation; import net.ripe.db.whois.common.rpsl.RpslAttribute; import net.ripe.db.whois.common.rpsl.RpslObject; +import net.ripe.db.whois.common.search.ManagedAttributeSearch; import net.ripe.db.whois.query.domain.DeletedVersionResponseObject; import net.ripe.db.whois.query.domain.VersionResponseObject; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +20,7 @@ import java.util.Iterator; import java.util.List; +import java.util.Objects; import static net.ripe.db.whois.api.rest.RestServiceHelper.getServerAttributeMapper; @@ -28,16 +31,20 @@ public class WhoisObjectServerMapper { private final AbuseContactSearch abuseContactSearch; private final ManagedAttributeSearch managedAttributeSearch; + private final List queryMessageGenerators; + @Autowired public WhoisObjectServerMapper( final WhoisObjectMapper whoisObjectMapper, final ResourceHolderSearch resourceHolderSearch, final AbuseContactSearch abuseContactSearch, - final ManagedAttributeSearch managedAttributeSearch) { + final ManagedAttributeSearch managedAttributeSearch, + final List queryMessageGenerators) { this.whoisObjectMapper = whoisObjectMapper; this.resourceHolderSearch = resourceHolderSearch; this.abuseContactSearch = abuseContactSearch; this.managedAttributeSearch = managedAttributeSearch; + this.queryMessageGenerators = queryMessageGenerators; } public List mapVersions(final List deleted, final List versions) { @@ -71,6 +78,17 @@ public void mapAbuseContact(final WhoisObject whoisObject, final Parameters para } } + public void mapObjectMessages(final WhoisObject whoisObject, final Parameters parameters, final RpslObject rpslObject){ + final ObjectMessages objectMessagesFormatted = new ObjectMessages(queryMessageGenerators + .stream() + .map(objectMessageGenerator -> objectMessageGenerator.generate(rpslObject, parameters)) + .filter(Objects::nonNull) + .toList()); + if (!objectMessagesFormatted.getMessages().isEmpty()){ + whoisObject.setObjectMessages(objectMessagesFormatted); + } + } + public void mapManagedAttributes(final WhoisObject whoisObject, final Parameters parameters, final RpslObject rpslObject) { if (Boolean.TRUE.equals(parameters.getManagedAttributes())) { whoisObject.setManaged(managedAttributeSearch.isCoMaintained(rpslObject)); diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rest/search/RpkiRoaMessageGenerator.java b/whois-api/src/main/java/net/ripe/db/whois/api/rest/search/RpkiRoaMessageGenerator.java new file mode 100644 index 0000000000..8b1c618993 --- /dev/null +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rest/search/RpkiRoaMessageGenerator.java @@ -0,0 +1,63 @@ +package net.ripe.db.whois.api.rest.search; + +import com.google.common.collect.ImmutableList; +import net.ripe.db.whois.api.rest.domain.RpslMessage; +import net.ripe.db.whois.api.rest.domain.Parameters; +import net.ripe.db.whois.common.rpki.Roa; +import net.ripe.db.whois.common.rpki.ValidationStatus; +import net.ripe.db.whois.common.rpki.WhoisRoaChecker; +import net.ripe.db.whois.common.rpsl.ObjectType; +import net.ripe.db.whois.common.rpsl.RpslObject; +import net.ripe.db.whois.query.QueryMessages; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Map; + + +@Component +public class RpkiRoaMessageGenerator implements RpslMessageGenerator { + + private static final ImmutableList TYPES = ImmutableList.of(ObjectType.ROUTE, ObjectType.ROUTE6); + private final boolean isEnabled; + + private final WhoisRoaChecker whoisRoaChecker; + + @Autowired + public RpkiRoaMessageGenerator(@Value("${roa.validator.available:false}") boolean isRoaChecker, final WhoisRoaChecker whoisRoaChecker) { + this.isEnabled = isRoaChecker; + this.whoisRoaChecker = whoisRoaChecker; + } + + @Override + public RpslMessage proceed(RpslObject rpslObject, Parameters parameters) { + if (!canProceed(parameters)){ + return null; + } + return validateRoa(rpslObject); + } + + @Override + public ImmutableList getTypes() { + return TYPES; + } + + private boolean canProceed(final Parameters parameters){ + return isEnabled && (parameters.getRoaCheck()!= null && parameters.getRoaCheck()); + } + + private RpslMessage validateRoa(final RpslObject rpslObject){ + final Map.Entry invalidRpkiRoa = whoisRoaChecker.validateAndGetInvalidRoa(rpslObject); + + if (invalidRpkiRoa == null) { + return null; + } + + return switch (invalidRpkiRoa.getValue()) { + case INVALID_ORIGIN -> new RpslMessage(QueryMessages.roaRouteOriginConflicts(rpslObject.getType().getName(), invalidRpkiRoa.getKey().getAsn())); + case INVALID_PREFIX_LENGTH -> new RpslMessage(QueryMessages.roaRoutePrefixLengthConflicts(rpslObject.getType().getName(), invalidRpkiRoa.getKey().getMaxLength())); + default -> new RpslMessage(QueryMessages.roaRouteConflicts(rpslObject.getType().getName(), invalidRpkiRoa.getKey().getMaxLength(), invalidRpkiRoa.getKey().getAsn())); + }; + } +} diff --git a/whois-api/src/main/java/net/ripe/db/whois/api/rest/search/RpslMessageGenerator.java b/whois-api/src/main/java/net/ripe/db/whois/api/rest/search/RpslMessageGenerator.java new file mode 100644 index 0000000000..dff7de614f --- /dev/null +++ b/whois-api/src/main/java/net/ripe/db/whois/api/rest/search/RpslMessageGenerator.java @@ -0,0 +1,21 @@ +package net.ripe.db.whois.api.rest.search; + +import com.google.common.collect.ImmutableList; +import net.ripe.db.whois.api.rest.domain.RpslMessage; +import net.ripe.db.whois.api.rest.domain.Parameters; +import net.ripe.db.whois.common.rpsl.ObjectType; +import net.ripe.db.whois.common.rpsl.RpslObject; + +public interface RpslMessageGenerator { + default RpslMessage generate(final RpslObject rpslObject, final Parameters parameters){ + if (!this.getTypes().contains(rpslObject.getType())){ + return null; + } + return proceed(rpslObject, parameters); + }; + + RpslMessage proceed(final RpslObject rpslObject, final Parameters parameters); + ImmutableList getTypes(); + + +} diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTest.java b/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTest.java index c201daf05e..67fa71980a 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTest.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTest.java @@ -56,7 +56,8 @@ public void search_disallowedFlags() { null, null, null, - null); + null, + false); fail("Disallowed option " + disallowedFlag + " did not throw error"); } catch (WebApplicationException e) { assertThat(((WhoisResources)e.getResponse().getEntity()).getErrorMessages().get(0).getText(), is("Disallowed search flag '%s'")); diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTestIntegration.java b/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTestIntegration.java index 31d20fde7a..0c46cfe541 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTestIntegration.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rest/WhoisSearchServiceTestIntegration.java @@ -1,6 +1,10 @@ package net.ripe.db.whois.api.rest; import com.google.common.base.Strings; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import net.ripe.db.whois.api.AbstractIntegrationTest; import net.ripe.db.whois.api.RestTest; import net.ripe.db.whois.api.rest.domain.Attribute; @@ -17,6 +21,7 @@ import net.ripe.db.whois.common.ApplicationVersion; import net.ripe.db.whois.common.MaintenanceMode; import net.ripe.db.whois.common.TestDateTimeProvider; +import net.ripe.db.whois.common.rpki.DummyRpkiDataProvider; import net.ripe.db.whois.common.rpsl.AttributeType; import net.ripe.db.whois.common.rpsl.RpslObject; import net.ripe.db.whois.common.sso.AuthServiceClient; @@ -34,10 +39,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.InetAddress; @@ -72,6 +73,9 @@ public class WhoisSearchServiceTestIntegration extends AbstractIntegrationTest { @Autowired private IpResourceConfiguration ipResourceConfiguration; + @Autowired + private DummyRpkiDataProvider rpkiDataProvider; + public static final String TEST_PERSON_STRING = "" + "person: Test Person\n" + "address: Singel 258\n" + @@ -1816,6 +1820,573 @@ public void search_inetnum_no_sources_given() { assertThat(hasSourceTest, is(true)); } + @Test + public void search_route_non_existing_roa_validation_enabled_as_json() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 200.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.json?query-string=200.4.0.0/16AS102&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(0)); + } + + @Test + public void search_less_specific_route_existing_roa_validation_enabled_as_json() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 176.223.251.0/24\n" + + "descr: Ripe test allocation\n" + + "origin: AS6505\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.json?query-string=176.223.251.0/24AS6505&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages().get(0).getText(), is("" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a less specific prefix %s.\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should " + + "either remove this route: object or update or delete the ROA.\n")); + } + + @Test + public void search_route_roa_validation_enabled_as_json() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 200.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.json?query-string=200.4.0.0/16AS102&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(0)); + } + + @Test + public void search_route6_roa_origin_mismatch_validation_enabled_as_json() { + databaseHelper.addObject(RpslObject.parse("" + + "route6: 2803:8240::/32\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.json?query-string=2803:8240::/32AS102&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(1)); + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages().get(0).getText(), is("" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a different origin AS%s.\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should " + + "either remove this route: object or update or delete the ROA.\n")); + } + + @Test + public void search_route_roa_origin_mismatch_validation_enabled_as_json() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 193.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.json?query-string=193.4.0.0/16AS102&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(1)); + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages().get(0).getText(), is("" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a different origin AS%s.\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should " + + "either remove this route: object or update or delete the ROA.\n")); + } + + @Test + public void search_route_roa_mismatch_no_validation_enabled_as_json() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 193.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.json?query-string=193.4.0.0/16AS102&roa-check=false&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(0)); + } + + @Test + public void search_route_roa_origin_mismatch_validation_enabled_as_xml() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 193.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=193.4.0.0/16AS102&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(1)); + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages().get(0).getText(), is("" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a different origin AS%s.\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should " + + "either remove this route: object or update or delete the ROA.\n")); + } + + @Test + public void search_route_roa_origin_and_prefix_length_mismatch_validation_enabled_as_xml() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 193.4.0.0/24\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final WhoisResources whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=193.4.0.0/24AS102&roa-check=true&flags=no-referenced") + .request() + .get(WhoisResources.class); + + assertThat(whoisResources.getWhoisObjects(), hasSize(1)); + + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages(), hasSize(1)); + assertThat(whoisResources.getWhoisObjects().get(0).getObjectMessages().getMessages().get(0).getText(), is("" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a less specific prefix %s and different origin AS%s.\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should " + + "either remove this route: object or update or delete the ROA.\n")); + } + + @Test + public void search_route6_roa_mismatch_less_specific_as_xml_strings() { + databaseHelper.addObject(RpslObject.parse("" + + "route6: 2803:8240::/33\n" + + "descr: Ripe test allocation\n" + + "origin: AS52511\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final String whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=2803:8240::/33AS52511&roa-check=true&flags=no-referenced") + .request() + .get(String.class); + + assertThat(whoisResources, is("" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n")); + } + + @Test + public void search_route6_roa_origin_mismatch_less_specific_as_xml_strings() { + databaseHelper.addObject(RpslObject.parse("" + + "route6: 2803:8240::/33\n" + + "descr: Ripe test allocation\n" + + "origin: AS123\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final String whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=2803:8240::/33AS123&roa-check=true&flags=no-referenced") + .request() + .get(String.class); + + assertThat(whoisResources, is("" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n")); + } + + @Test + public void search_route6_roa_more_specific_as_xml_strings() { + databaseHelper.addObject(RpslObject.parse("" + + "route6: 2803::/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS123\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final String whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=2803::/16AS123&roa-check=true&flags=no-referenced") + .request() + .get(String.class); + + assertThat(whoisResources, is("" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n")); + } + + @Test + public void search_route6_roa_mismatch_origin_as_xml_strings() { + databaseHelper.addObject(RpslObject.parse("" + + "route6: 2803:8240::/32\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final String whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=2803:8240::/32AS102&roa-check=true&flags=no-referenced") + .request() + .get(String.class); + + assertThat(whoisResources, is("" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n")); + } + + @Test + public void search_route_roa_mismatch_validation_enabled_as_xml_strings() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 193.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final String whoisResources = RestTest.target(getPort(), "whois/search.xml?query-string=193.4.0.0/16AS102&roa-check=true&flags=no-referenced") + .request() + .get(String.class); + + assertThat(whoisResources, is("" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n")); + } + + @Test + public void search_route_roa_mismatch_validation_enabled_as_txt() { + databaseHelper.addObject(RpslObject.parse("" + + "route: 193.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n")); + ipTreeUpdater.rebuild(); + + final String whoisResources = RestTest.target(getPort(), "whois/search.txt?query-string=193.4.0.0/16AS102&roa-check=true&flags=no-referenced") + .request() + .get(String.class); + + // txt must show just the war object - Jira DB-3867, no including comments + assertThat(whoisResources, is(""+ + "route: 193.4.0.0/16\n" + + "descr: Ripe test allocation\n" + + "origin: AS102\n" + + "admin-c: TP1-TEST\n" + + "mnt-by: OWNER-MNT\n" + + "mnt-lower: OWNER-MNT\n" + + "source: TEST-NONAUTH\n" + + "\n")); + } + @Test public void search_route_no_sources_given() { databaseHelper.addObject(RpslObject.parse("" + diff --git a/whois-api/src/test/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapperTest.java b/whois-api/src/test/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapperTest.java index 0e7280e0ba..fcce795ffa 100644 --- a/whois-api/src/test/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapperTest.java +++ b/whois-api/src/test/java/net/ripe/db/whois/api/rest/mapper/WhoisObjectServerMapperTest.java @@ -9,7 +9,6 @@ import net.ripe.db.whois.api.rest.domain.WhoisObject; import net.ripe.db.whois.api.rest.domain.WhoisVersion; import net.ripe.db.whois.api.rest.search.AbuseContactSearch; -import net.ripe.db.whois.common.search.ManagedAttributeSearch; import net.ripe.db.whois.api.rest.search.ResourceHolderSearch; import net.ripe.db.whois.common.dao.VersionDateTime; import net.ripe.db.whois.common.domain.CIString; @@ -17,6 +16,7 @@ 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.search.ManagedAttributeSearch; import net.ripe.db.whois.query.domain.DeletedVersionResponseObject; import net.ripe.db.whois.query.domain.VersionResponseObject; import org.junit.jupiter.api.BeforeEach; @@ -67,7 +67,7 @@ public void setup() { new FormattedServerAttributeMapper(referencedTypeResolver, sourceResolver, BASE_URL), new FormattedClientAttributeMapper() }); - whoisObjectServerMapper = new WhoisObjectServerMapper(whoisObjectMapper, resourceHolderSearch, abuseContactSearch, managedAttributeSearch); + whoisObjectServerMapper = new WhoisObjectServerMapper(whoisObjectMapper, resourceHolderSearch, abuseContactSearch, managedAttributeSearch, Lists.newArrayList()); lenient().when(parameters.getUnformatted()).thenReturn(Boolean.FALSE); lenient().when(sourceResolver.getSource(anyString(), any(CIString.class), anyString())).thenReturn("test"); } diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessage.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessage.java index c85c59cef5..fb0449487c 100644 --- a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessage.java +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessage.java @@ -1,116 +1,35 @@ package net.ripe.db.whois.api.rest.domain; import com.fasterxml.jackson.annotation.JsonInclude; -import com.google.common.collect.Lists; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; -import jakarta.xml.bind.annotation.XmlAttribute; -import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import net.ripe.db.whois.common.Message; -import net.ripe.db.whois.common.Messages; import net.ripe.db.whois.common.rpsl.RpslAttribute; -import javax.annotation.Nullable; import java.util.List; -import java.util.Objects; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "errormessage") @JsonInclude(NON_EMPTY) -public class ErrorMessage implements Comparable { - - @XmlAttribute(required = true) - private String severity; // TODO: severity should be enum - @XmlElement - private Attribute attribute; - @XmlAttribute(required = true) - private String text; - @XmlElement - private List args; - - ErrorMessage(final String severity, final Attribute attribute, final String text, final List args) { - this.severity = severity; - this.attribute = attribute; - this.text = text; - this.args = args; - } - - public ErrorMessage(final Message message) { - this.severity = message.getType().toString(); - this.attribute = null; - this.text = message.getText(); - this.args = Lists.newArrayList(); - for (Object arg : message.getArgs()) { - this.args.add(new Arg(arg.toString())); - } - } - - public ErrorMessage(final Message message, final RpslAttribute attribute) { - this(message); - this.attribute = new Attribute(attribute.getKey(), attribute.getValue()); - } +public class ErrorMessage extends WhoisMessage { public ErrorMessage() { - this.args = Lists.newArrayList(); + super(); } - @Nullable - public String getSeverity() { - return severity; + ErrorMessage(final Attribute attribute, final String text, final List args) { + super("Error", attribute, text, args); } - @Nullable - public Attribute getAttribute() { - return attribute; - } - - @Nullable - public String getText() { - return text; - } - - @Nullable - public List getArgs() { - return args; - } - - @Override - public String toString() { - return (args == null || args.isEmpty() || text == null) ? - text : - String.format(text, args.toArray()); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - - if (o == null || (o.getClass() != getClass())) { - return false; - } - - final ErrorMessage errorMessage = (ErrorMessage)o; - - return (Objects.equals(severity, errorMessage.getSeverity()) && - Objects.equals(attribute, errorMessage.getAttribute()) && - Objects.equals(text, errorMessage.getText()) && - Objects.equals(args, errorMessage.getArgs())); + public ErrorMessage(final Message message) { + super(message); } - @Override - public int hashCode() { - return Objects.hash(severity, attribute, text, args); + public ErrorMessage(final Message message, final RpslAttribute attribute) { + super(message, attribute); } - @Override - public int compareTo(final ErrorMessage errorMessage) { - final Messages.Type thisType = Messages.Type.valueOf(severity.toUpperCase()); - final Messages.Type otherType = Messages.Type.valueOf(errorMessage.getSeverity().toUpperCase()); - return thisType.compareTo(otherType); - } } diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessages.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessages.java index 5c9985c53e..0313a5f947 100644 --- a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessages.java +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ErrorMessages.java @@ -14,7 +14,7 @@ public class ErrorMessages { @XmlElement(name = "errormessage") - private List errorMessages; + private final List errorMessages; public ErrorMessages(final List errorMessages) { this.errorMessages = errorMessages; diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ObjectMessages.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ObjectMessages.java new file mode 100644 index 0000000000..acbee03419 --- /dev/null +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/ObjectMessages.java @@ -0,0 +1,39 @@ +package net.ripe.db.whois.api.rest.domain; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.google.common.collect.Lists; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; + +import java.util.List; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; + +@XmlRootElement(name = "objectmessages") +@XmlAccessorType(XmlAccessType.FIELD) +@JsonInclude(NON_EMPTY) +public class ObjectMessages { + @XmlElement(name = "objectmessage") + protected List objectMessages; + public ObjectMessages() { + objectMessages = Lists.newArrayList(); + } + + public ObjectMessages(final List objectMessages) { + this.objectMessages = objectMessages; + } + + public void addMessage(final RpslMessage objectMessage){ + objectMessages.add(objectMessage); + } + + public void setMessages(final List objectMessages) { + this.objectMessages = objectMessages; + } + + public List getMessages() { + return objectMessages; + } +} diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/Parameters.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/Parameters.java index 23ddb2a7cc..46ed34cb54 100644 --- a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/Parameters.java +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/Parameters.java @@ -57,6 +57,9 @@ public class Parameters { @XmlTransient private Boolean unformatted; + @XmlTransient + private Boolean roaCheck; + public Parameters( final InverseAttributes inverseAttributes, final String client, @@ -70,7 +73,8 @@ public Parameters( final Boolean abuseContact, final Integer limit, final Integer offset, - final Boolean unformatted) { + final Boolean unformatted, + final Boolean roaCheck) { this.inverseAttributes = inverseAttributes; this.typeFilters = typeFilters; this.flags = flags; @@ -84,6 +88,7 @@ public Parameters( this.offset = offset; this.unformatted = unformatted; this.client = client; + this.roaCheck = roaCheck; } public Parameters() { @@ -140,6 +145,10 @@ public Boolean getUnformatted() { public String getClient() { return client; } + public Boolean getRoaCheck() { + return roaCheck; + } + public static class Builder { private InverseAttributes inverseAttributes; @@ -155,6 +164,7 @@ public static class Builder { private Integer limit; private Integer offset; private Boolean unformatted; + private Boolean roaCheck; public Builder inverseAttributes(final InverseAttributes inverseAttributes) { this.inverseAttributes = inverseAttributes; @@ -221,6 +231,11 @@ public Builder unformatted(final Boolean unformatted) { return this; } + public Builder roaCheck(final Boolean roaCheck) { + this.roaCheck = roaCheck; + return this; + } + public Parameters build() { return new Parameters( inverseAttributes, @@ -235,7 +250,8 @@ public Parameters build() { abuseContact, limit, offset, - unformatted); + unformatted, + roaCheck); } } } diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/RpslMessage.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/RpslMessage.java new file mode 100644 index 0000000000..4fd050d480 --- /dev/null +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/RpslMessage.java @@ -0,0 +1,24 @@ +package net.ripe.db.whois.api.rest.domain; + +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlRootElement; +import net.ripe.db.whois.common.Message; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "objectmessage") +@JsonInclude(NON_EMPTY) +public class RpslMessage extends WhoisMessage { + + public RpslMessage(){ + super(); + } + + public RpslMessage(final Message message) { + super(message); + } + +} diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisMessage.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisMessage.java new file mode 100644 index 0000000000..870d1d995d --- /dev/null +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisMessage.java @@ -0,0 +1,107 @@ +package net.ripe.db.whois.api.rest.domain; + +import com.google.common.collect.Lists; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import net.ripe.db.whois.common.Message; +import net.ripe.db.whois.common.Messages; +import net.ripe.db.whois.common.rpsl.RpslAttribute; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +public abstract class WhoisMessage implements Comparable{ + + @XmlAttribute(required = true) + protected String severity; // TODO: severity should be enum + @XmlElement + protected Attribute attribute; + @XmlAttribute(required = true) + protected String text; + @XmlElement + protected List args; + + public WhoisMessage(final String severity, final Attribute attribute, final String text, final List args) { + this.severity = severity; + this.attribute = attribute; + this.text = text; + this.args = args; + } + + public WhoisMessage(final Message message) { + this.severity = message.getType().toString(); + this.attribute = null; + this.text = message.getText(); + this.args = Lists.newArrayList(); + for (Object arg : message.getArgs()) { + this.args.add(new Arg(arg.toString())); + } + } + + public WhoisMessage(final Message message, final RpslAttribute attribute) { + this(message); + this.attribute = new Attribute(attribute.getKey(), attribute.getValue()); + } + + public WhoisMessage() { + this.args = Lists.newArrayList(); + } + + @Nullable + public String getSeverity() { + return severity; + } + + @Nullable + public Attribute getAttribute() { + return attribute; + } + + @Nullable + public String getText() { + return text; + } + + @Nullable + public List getArgs() { + return args; + } + + @Override + public String toString() { + return (args == null || args.isEmpty() || text == null) ? + text : + String.format(text, args.toArray()); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + + if (o == null || (o.getClass() != getClass())) { + return false; + } + + final ErrorMessage errorMessage = (ErrorMessage)o; + + return (Objects.equals(severity, errorMessage.getSeverity()) && + Objects.equals(attribute, errorMessage.getAttribute()) && + Objects.equals(text, errorMessage.getText()) && + Objects.equals(args, errorMessage.getArgs())); + } + + @Override + public int hashCode() { + return Objects.hash(severity, attribute, text, args); + } + + @Override + public int compareTo(final WhoisMessage errorMessage) { + final Messages.Type thisType = Messages.Type.valueOf(severity.toUpperCase()); + final Messages.Type otherType = Messages.Type.valueOf(errorMessage.getSeverity().toUpperCase()); + return thisType.compareTo(otherType); + } +} diff --git a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisObject.java b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisObject.java index a8800435be..ea5677cacd 100644 --- a/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisObject.java +++ b/whois-client/src/main/java/net/ripe/db/whois/api/rest/domain/WhoisObject.java @@ -1,13 +1,13 @@ package net.ripe.db.whois.api.rest.domain; import com.fasterxml.jackson.annotation.JsonInclude; - import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlType; + import java.util.Collections; import java.util.List; @@ -24,6 +24,7 @@ "resourceHolder", "abuseContact", "managed", + "objectMessages", }) @JsonInclude(NON_EMPTY) @XmlRootElement(name = "object") @@ -59,6 +60,9 @@ public class WhoisObject { @XmlAttribute private Integer version; + @XmlElement(name = "objectmessages") + private ObjectMessages objectMessages; + public WhoisObject() { // required no-arg constructor } @@ -73,7 +77,8 @@ private WhoisObject( final Integer version, final ResourceHolder resourceHolder, final AbuseContact abuseContact, - final Boolean managed) { + final Boolean managed, + final ObjectMessages objectMessages) { this.link = link; this.source = source; this.primaryKey = primaryKey; @@ -84,8 +89,11 @@ private WhoisObject( this.resourceHolder = resourceHolder; this.abuseContact = abuseContact; this.managed = managed; + this.objectMessages = objectMessages; } + + // builder public static class Builder { @@ -100,6 +108,8 @@ public static class Builder { private AbuseContact abuseContact; private Boolean managed; + private ObjectMessages objectMessages; + public Builder link(final Link link) { this.link = link; return this; @@ -160,6 +170,11 @@ public Builder managed(final Boolean managed) { return this; } + public Builder objectMessages(final ObjectMessages objectMessages) { + this.objectMessages = objectMessages; + return this; + } + public WhoisObject build() { return new WhoisObject( link, @@ -171,7 +186,8 @@ public WhoisObject build() { version, resourceHolder, abuseContact, - managed); + managed, + objectMessages); } } @@ -249,6 +265,14 @@ public void setAbuseContact(final AbuseContact abuseContact) { this.abuseContact = abuseContact; } + public void setObjectMessages(final ObjectMessages objectMessages) { + this.objectMessages = objectMessages; + } + + public ObjectMessages getObjectMessages() { + return objectMessages != null ? objectMessages : new ObjectMessages(); + } + public Boolean isManaged() { return managed; } diff --git a/whois-client/src/main/java/net/ripe/db/whois/query/QueryFlag.java b/whois-client/src/main/java/net/ripe/db/whois/query/QueryFlag.java index e95a4ac087..08fa4ee257 100644 --- a/whois-client/src/main/java/net/ripe/db/whois/query/QueryFlag.java +++ b/whois-client/src/main/java/net/ripe/db/whois/query/QueryFlag.java @@ -257,8 +257,8 @@ public String toString() { return toString; } - private static Map VALID_LONG_FLAGS; - private static Map VALID_SHORT_FLAGS; + private static final Map VALID_LONG_FLAGS; + private static final Map VALID_SHORT_FLAGS; static { final ImmutableMap.Builder validLongFlags = ImmutableMap.builder(); diff --git a/whois-client/src/main/java/net/ripe/db/whois/query/QueryMessages.java b/whois-client/src/main/java/net/ripe/db/whois/query/QueryMessages.java index be6f8a8e07..7da3663cfb 100644 --- a/whois-client/src/main/java/net/ripe/db/whois/query/QueryMessages.java +++ b/whois-client/src/main/java/net/ripe/db/whois/query/QueryMessages.java @@ -7,8 +7,6 @@ import net.ripe.db.whois.common.domain.Hosts; import org.apache.commons.lang.StringUtils; -import java.net.InetAddress; - import static net.ripe.db.whois.common.Messages.Type; public final class QueryMessages { @@ -62,6 +60,30 @@ public static Message unvalidatedAbuseCShown(final CharSequence key, final CharS "\nAbuse-mailbox validation failed. Please refer to %s for further information.", key, value, orgId); } + public static Message roaRouteOriginConflicts(final String objectType, final long asn){ + return new QueryMessage(Type.WARNING, "" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a different origin AS%s." + + "\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should" + + " either remove this route: object or update or delete the ROA.", objectType, asn); + } + + public static Message roaRoutePrefixLengthConflicts(final String objectType, final int prefix){ + return new QueryMessage(Type.WARNING, "" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a less specific prefix %s." + + "\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should" + + " either remove this route: object or update or delete the ROA.", objectType, prefix); + } + + public static Message roaRouteConflicts(final String objectType, final int prefix, final long asn){ + return new QueryMessage(Type.WARNING, "" + + "Warning: this %s object conflicts with an overlapping RPKI ROA with a less specific prefix %s and different origin AS%s." + + "\n" + + "As a result an announcement for this prefix may be rejected by many autonomous systems. You should" + + " either remove this route: object or update or delete the ROA.", objectType, prefix, asn); + } + public static Message unvalidatedAbuseCShown(final CharSequence key, final CharSequence value) { return new QueryMessage(Type.INFO, "Abuse contact for '%s' is '%s'", key, value); } diff --git a/whois-client/src/test/java/net/ripe/db/whois/api/rest/domain/ErrorMessageTest.java b/whois-client/src/test/java/net/ripe/db/whois/api/rest/domain/ErrorMessageTest.java index d7072f5648..c789571925 100644 --- a/whois-client/src/test/java/net/ripe/db/whois/api/rest/domain/ErrorMessageTest.java +++ b/whois-client/src/test/java/net/ripe/db/whois/api/rest/domain/ErrorMessageTest.java @@ -6,9 +6,9 @@ import net.ripe.db.whois.common.rpsl.RpslAttribute; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; public class ErrorMessageTest { @@ -34,11 +34,11 @@ public void to_string_default_constructor() { @Test public void equals() { - final ErrorMessage errorMessage1 = new ErrorMessage("Error", new Attribute("name", "value"), "text", Lists.newArrayList(new Arg("value"))); + final ErrorMessage errorMessage1 = new ErrorMessage(new Attribute("name", "value"), "text", Lists.newArrayList(new Arg("value"))); assertThat(errorMessage1.equals(null), is(false)); assertThat(errorMessage1.equals("String"), is(false)); assertThat(errorMessage1.equals(errorMessage1), is(true)); - assertThat(errorMessage1.equals(new ErrorMessage("Error", new Attribute("name", "value"), "text", Lists.newArrayList(new Arg("value")))), is(true)); + assertThat(errorMessage1.equals(new ErrorMessage(new Attribute("name", "value"), "text", Lists.newArrayList(new Arg("value")))), is(true)); final ErrorMessage errorMessage2 = new ErrorMessage(new Message(Messages.Type.ERROR, "text")); assertThat(errorMessage2.equals(null), is(false)); diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RoutinatorDataProvider.java b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RoutinatorDataProvider.java index 1b1828c068..831d99c396 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RoutinatorDataProvider.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RoutinatorDataProvider.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider; import com.google.common.base.Strings; import com.google.common.collect.Lists; +import jakarta.ws.rs.ClientErrorException; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.core.MediaType; @@ -49,10 +50,15 @@ public List loadRoas() { return Lists.newArrayList(); } - return this.client.target(rpkiBaseUrl) - .path("json") - .request(MediaType.APPLICATION_JSON_TYPE) - .get(Roas.class) - .getRoas(); + try { + return this.client.target(rpkiBaseUrl) + .path("json") + .request(MediaType.APPLICATION_JSON_TYPE) + .get(Roas.class) + .getRoas(); + } catch (ClientErrorException ex){ + LOGGER.error("RPKI service returned an error, so the ROAs are not updated"); + return Lists.newArrayList(); + } } } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RpkiRoaChecker.java b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RpkiRoaChecker.java index a44b0dcaa9..7cd0afedaa 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RpkiRoaChecker.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/RpkiRoaChecker.java @@ -42,7 +42,7 @@ public Map validateRoas(final RpslObject route) { return validationResult; } - private ValidationStatus validate(final RpslObject route, final Roa roa, final IpInterval prefix) { + protected ValidationStatus validate(final RpslObject route, final Roa roa, final IpInterval prefix) { final long nonAuthAsn = Asn.parse(route.getValueForAttribute(AttributeType.ORIGIN).toString()).asBigInteger().longValue(); return prefix.getPrefixLength() <= roa.getMaxLength() && nonAuthAsn != 0 && diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/ValidationStatus.java b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/ValidationStatus.java index 7dc53f4b31..d31bce5543 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/ValidationStatus.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/ValidationStatus.java @@ -4,5 +4,7 @@ public enum ValidationStatus { NOT_FOUND, VALID, - INVALID + INVALID, + INVALID_ORIGIN, + INVALID_PREFIX_LENGTH } diff --git a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/WhoisRoaChecker.java b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/WhoisRoaChecker.java index 12f7100b55..7d0db9f75e 100644 --- a/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/WhoisRoaChecker.java +++ b/whois-commons/src/main/java/net/ripe/db/whois/common/rpki/WhoisRoaChecker.java @@ -1,13 +1,21 @@ package net.ripe.db.whois.common.rpki; +import net.ripe.commons.ip.Asn; +import net.ripe.db.whois.common.ip.IpInterval; +import net.ripe.db.whois.common.rpsl.AttributeType; import net.ripe.db.whois.common.rpsl.RpslObject; +import org.apache.commons.compress.utils.Lists; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Map; import java.util.Optional; import static net.ripe.db.whois.common.rpki.ValidationStatus.INVALID; +import static net.ripe.db.whois.common.rpki.ValidationStatus.INVALID_ORIGIN; +import static net.ripe.db.whois.common.rpki.ValidationStatus.INVALID_PREFIX_LENGTH; +import static net.ripe.db.whois.common.rpki.ValidationStatus.VALID; @Component public class WhoisRoaChecker extends RpkiRoaChecker { @@ -15,16 +23,33 @@ public WhoisRoaChecker(final RpkiService rpkiService) { super(rpkiService); } - public Roa validateAndGetInvalidRoa(final RpslObject route){ + public Map.Entry validateAndGetInvalidRoa(final RpslObject route){ final Optional> roaStatusMap = validateRoas(route) .entrySet() .stream() - .filter(entry -> entry.getValue() == INVALID) + .filter(entry -> INVALID.equals(entry.getValue()) || INVALID_PREFIX_LENGTH.equals(entry.getValue()) || INVALID_ORIGIN.equals(entry.getValue())) .findFirst(); if (roaStatusMap.isEmpty()){ return null; } - return roaStatusMap.get().getKey(); + return roaStatusMap.get(); + } + @Override + protected ValidationStatus validate(final RpslObject route, final Roa roa, final IpInterval prefix) { + final List invalidStatus = Lists.newArrayList(); + final long nonAuthAsn = Asn.parse(route.getValueForAttribute(AttributeType.ORIGIN).toString()).asBigInteger().longValue(); + if (prefix.getPrefixLength() > roa.getMaxLength()){ + invalidStatus.add(INVALID_PREFIX_LENGTH); + } + + if (nonAuthAsn != 0 && nonAuthAsn != roa.getAsn()){ + invalidStatus.add(INVALID_ORIGIN); + } + + if (invalidStatus.isEmpty()){ + return VALID; + } + return invalidStatus.size() == 1 ? invalidStatus.get(0) : INVALID; } } diff --git a/whois-commons/src/test/java/net/ripe/db/whois/common/rpki/DummyRpkiDataProvider.java b/whois-commons/src/test/java/net/ripe/db/whois/common/rpki/DummyRpkiDataProvider.java index d044e0748b..4689422c18 100644 --- a/whois-commons/src/test/java/net/ripe/db/whois/common/rpki/DummyRpkiDataProvider.java +++ b/whois-commons/src/test/java/net/ripe/db/whois/common/rpki/DummyRpkiDataProvider.java @@ -1,9 +1,11 @@ package net.ripe.db.whois.common.rpki; +import com.fasterxml.jackson.databind.ObjectMapper; import net.ripe.db.whois.common.profiles.WhoisProfile; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; +import java.io.IOException; import java.util.Collections; import java.util.List; @@ -11,17 +13,14 @@ @Profile({WhoisProfile.TEST}) public class DummyRpkiDataProvider implements RpkiDataProvider { - private List roas; - @Override public List loadRoas() { - if (roas == null){ - return Collections.emptyList(); + try { + return new ObjectMapper().readValue(getClass().getResourceAsStream("/rpki/roas.json"), Roas.class).getRoas(); + } catch (IOException ex){ + /* Do Nothing*/ } - return roas; + return Collections.emptyList(); } - public void setRoas(final List roas) { - this.roas = roas; - } } diff --git a/whois-commons/src/test/resources/rpki/roas.json b/whois-commons/src/test/resources/rpki/roas.json index f472e291a5..2de4d7ad20 100644 --- a/whois-commons/src/test/resources/rpki/roas.json +++ b/whois-commons/src/test/resources/rpki/roas.json @@ -1,7 +1,7 @@ { "roas": [ { "asn": "AS42187", "prefix": "94.127.92.0/24", "maxLength": 24, "ta": "ripe" }, - { "asn": "AS52511", "prefix": "2803:8240::/32", "maxLength": 48, "ta": "lacnic" }, + { "asn": "AS52511", "prefix": "2803:8240::/32", "maxLength": 32, "ta": "lacnic" }, { "asn": "AS16243", "prefix": "217.114.96.0/20", "maxLength": 24, "ta": "ripe" }, { "asn": "AS44889", "prefix": "46.38.144.0/23", "maxLength": 24, "ta": "ripe" }, { "asn": "AS51018", "prefix": "95.159.84.0/22", "maxLength": 22, "ta": "ripe" }, @@ -21,6 +21,11 @@ { "asn": "AS136400", "prefix": "103.86.126.0/24", "maxLength": 24, "ta": "apnic" }, { "asn": "AS136255", "prefix": "2400:ac40:8b7::/48", "maxLength": 48, "ta": "apnic" }, { "asn": "AS62217", "prefix": "91.220.127.0/24", "maxLength": 24, "ta": "ripe" }, - { "asn": "AS7604", "prefix": "202.74.188.0/22", "maxLength": 22, "ta": "apnic" } + { "asn": "AS6505", "prefix": "206.48.0.0/16", "maxLength": 16, "ta": "arin" }, + { "asn": "AS5511", "prefix": "206.48.0.0/16", "maxLength": 16, "ta": "arin" }, + { "asn": "AS1964", "prefix": "206.48.0.0/16", "maxLength": 16, "ta": "arin" }, + { "asn": "AS102", "prefix": "193.4.0.0/16", "maxLength": 16, "ta": "arin" }, + { "asn": "AS6505", "prefix": "193.4.0.0/16", "maxLength": 16, "ta": "arin" }, + { "asn": "AS6505", "prefix": "176.223.250.0/23", "maxLength": 23, "ta": "ripe" } ] } \ No newline at end of file diff --git a/whois-commons/src/test/resources/whois.properties b/whois-commons/src/test/resources/whois.properties index 901805f212..305761f487 100644 --- a/whois-commons/src/test/resources/whois.properties +++ b/whois-commons/src/test/resources/whois.properties @@ -171,3 +171,5 @@ instance.name=localhost #Dump size limit in MB dump.total.size.limit= 15 +roa.validator.available=true + diff --git a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/BaseEndToEndSpec.groovy b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/BaseEndToEndSpec.groovy index ecd21bbca8..41dd339dee 100644 --- a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/BaseEndToEndSpec.groovy +++ b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/BaseEndToEndSpec.groovy @@ -1,9 +1,8 @@ package net.ripe.db.whois.spec import jakarta.mail.Address -import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.core.MultivaluedMap import net.ripe.db.whois.WhoisFixture -import net.ripe.db.whois.spec.domain.Message import net.ripe.db.whois.common.TestDateTimeProvider import net.ripe.db.whois.common.rpsl.AttributeType import net.ripe.db.whois.common.rpsl.ObjectType @@ -11,6 +10,7 @@ import net.ripe.db.whois.common.rpsl.RpslAttribute import net.ripe.db.whois.common.rpsl.RpslObject import net.ripe.db.whois.query.support.TestWhoisLog import net.ripe.db.whois.spec.domain.AckResponse +import net.ripe.db.whois.spec.domain.Message import net.ripe.db.whois.spec.domain.NotificationResponse import net.ripe.db.whois.spec.domain.SyncUpdate import net.ripe.db.whois.spec.domain.SyncUpdateResponse @@ -306,6 +306,10 @@ ${response} return whoisFixture.getDatabaseHelper() } + def getRpkiDataProvider(){ + return whoisFixture.getRpkiDataProvider(); + } + def getIpTreeUpdater() { return whoisFixture.getIpTreeUpdater() } diff --git a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/SearchQuerySpec.groovy b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/SearchQuerySpec.groovy index 9795ab9a01..2e92d51dc7 100644 --- a/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/SearchQuerySpec.groovy +++ b/whois-endtoend/src/test/groovy/net/ripe/db/whois/spec/integration/SearchQuerySpec.groovy @@ -1,6 +1,5 @@ package net.ripe.db.whois.spec.integration - import net.ripe.db.whois.common.source.Source @org.junit.jupiter.api.Tag("IntegrationTest") @@ -47,6 +46,20 @@ class SearchQuerySpec extends BaseWhoisSourceSpec { mnt-ref: UPD-MNT mnt-by: UPD-MNT source: TEST + """, + "ROUTE": """\ + route: 193.4.0.0/16 + descr: Route + origin: AS102 + mnt-by: ADMIN-MNT + source: TEST + """, + "ROUTE6": """\ + route6: 2001:1578:0200::/40 + descr: TEST-ROUTE6 + origin: AS12726 + mnt-by: ADMIN-MNT + source: TEST """ ] } diff --git a/whois-endtoend/src/test/java/net/ripe/db/whois/WhoisFixture.java b/whois-endtoend/src/test/java/net/ripe/db/whois/WhoisFixture.java index 1c63e4cd84..9ec66ed352 100644 --- a/whois-endtoend/src/test/java/net/ripe/db/whois/WhoisFixture.java +++ b/whois-endtoend/src/test/java/net/ripe/db/whois/WhoisFixture.java @@ -27,6 +27,7 @@ import net.ripe.db.whois.common.grs.AuthoritativeResourceData; import net.ripe.db.whois.common.iptree.IpTreeUpdater; import net.ripe.db.whois.common.profiles.WhoisProfile; +import net.ripe.db.whois.common.rpki.DummyRpkiDataProvider; import net.ripe.db.whois.common.rpsl.ObjectType; import net.ripe.db.whois.common.rpsl.RpslObject; import net.ripe.db.whois.common.source.SourceAwareDataSource; @@ -85,6 +86,8 @@ public class WhoisFixture { protected WhoisServer whoisServer; protected RestClient restClient; protected WhoisRestService whoisRestService; + + protected DummyRpkiDataProvider rpkiDataProvider; protected TestWhoisLog testWhoisLog; static { @@ -131,6 +134,7 @@ public void start() throws Exception { restClient = applicationContext.getBean(RestClient.class); whoisRestService = applicationContext.getBean(WhoisRestService.class); testWhoisLog = applicationContext.getBean(TestWhoisLog.class); + rpkiDataProvider = applicationContext.getBean(DummyRpkiDataProvider.class); databaseHelper.setup(); whoisServer.start(); @@ -249,6 +253,9 @@ public DatabaseHelper getDatabaseHelper() { return databaseHelper; } + public DummyRpkiDataProvider getRpkiDataProvider(){ + return rpkiDataProvider; + } public AuthoritativeResourceDao getAuthoritativeResourceDao() { return authoritativeResourceDao; } diff --git a/whois-query/src/main/java/net/ripe/db/whois/query/query/Query.java b/whois-query/src/main/java/net/ripe/db/whois/query/query/Query.java index bc8374bc90..9ae85a875c 100644 --- a/whois-query/src/main/java/net/ripe/db/whois/query/query/Query.java +++ b/whois-query/src/main/java/net/ripe/db/whois/query/query/Query.java @@ -60,8 +60,8 @@ public class Query { // TODO: [AH] these fields should be part of QueryContext, not Query private List passwords; private String ssoToken; - private Origin origin; - private boolean trusted; + private final Origin origin; + private final boolean trusted; // TODO: [AH] we should use -x flag for direct match for all object types instead of this hack private boolean matchPrimaryKeyOnly;