diff --git a/build.gradle b/build.gradle index ed710f4..e13254a 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ dependencies { implementation "org.thymeleaf:thymeleaf:3.1.1.RELEASE" implementation "org.thymeleaf:thymeleaf-spring5:3.1.1.RELEASE" - implementation platform('io.sentry:sentry-bom:6.28.0') + implementation platform('io.sentry:sentry-bom:6.34.0') implementation 'io.sentry:sentry-spring-boot-starter' implementation 'io.sentry:sentry-logback' @@ -58,8 +58,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.jamesmurty.utils:java-xmlbuilder:1.3' - implementation 'commons-codec:commons-codec:1.16.0' - implementation 'commons-io:commons-io:2.14.0' + implementation 'commons-codec:commons-codec:1.16.1' + implementation 'commons-io:commons-io:2.15.1' implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5' implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5' implementation 'net.logstash.logback:logstash-logback-encoder:7.3' @@ -74,7 +74,7 @@ dependencies { } testImplementation 'com.github.tomakehurst:wiremock-jre8:2.35.0' - testImplementation 'net.jqwik:jqwik:1.7.4' + testImplementation 'net.jqwik:jqwik:1.8.3' testImplementation "net.ripe.rpki:rpki-commons:$rpki_commons_version:tests" testImplementation 'org.assertj:assertj-core' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index f5dcb2d..0364ea0 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -9,10 +9,10 @@ repositories { } dependencies { - implementation 'io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.4' + implementation 'io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.6' implementation('com.gorylenko.gradle-git-properties:com.gorylenko.gradle-git-properties.gradle.plugin:2.4.1') { exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit' } - implementation 'org.eclipse.jgit:org.eclipse.jgit:5.13.2.202306221912-r' + implementation 'org.eclipse.jgit:org.eclipse.jgit:5.13.3.202401111512-r' implementation 'org.sonarqube:org.sonarqube.gradle.plugin:4.2.1.3168' } diff --git a/dependencies.gradle b/dependencies.gradle index 507f781..bc3ee61 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext { - rpki_commons_version = '1.36' - spring_boot_version = '2.7.16' + rpki_commons_version = '1.37' + spring_boot_version = '2.7.18' } diff --git a/src/main/dist/rpki-ripe-ncc.sh b/src/main/dist/rpki-ripe-ncc.sh index 5e64213..0101529 100755 --- a/src/main/dist/rpki-ripe-ncc.sh +++ b/src/main/dist/rpki-ripe-ncc.sh @@ -53,7 +53,7 @@ esac JAVA_OPTS=( "-DAPPLICATION_ENVIRONMENT=$APPLICATION_ENVIRONMENT" "-Dinstance.name=$(hostname)" - "-XX:+UseParallelOldGC" + "-XX:+UseParallelGC" "-Xlog:gc:$LOG_DIR/gc.log:utctime" "-XX:+HeapDumpOnOutOfMemoryError" "-XX:HeapDumpPath=$LOG_DIR" "-XX:+ExitOnOutOfMemoryError" "${ENV_OPTS[@]}" diff --git a/src/main/java/net/ripe/rpki/application/impl/CommandAuditServiceBean.java b/src/main/java/net/ripe/rpki/application/impl/CommandAuditServiceBean.java index c24fd13..bffa632 100644 --- a/src/main/java/net/ripe/rpki/application/impl/CommandAuditServiceBean.java +++ b/src/main/java/net/ripe/rpki/application/impl/CommandAuditServiceBean.java @@ -20,6 +20,7 @@ import javax.persistence.Query; import javax.security.auth.x500.X500Principal; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @@ -127,4 +128,17 @@ public void deleteCommandsForCa(long caId) { query.setParameter("id", caId); query.executeUpdate(); } + + @Override + @SuppressWarnings("unchecked") + public Map findMentionsInSummary(String item) { + Query query = entityManager.createQuery( + "SELECT ca FROM CommandAudit ca " + + "WHERE commandSummary LIKE :itemSpaces " + + "OR commandSummary LIKE :itemParens"); + query.setParameter("itemSpaces", "% " + item + " %"); + query.setParameter("itemParens", "%'" + item + "'%"); + List commands = query.getResultList(); + return commands.stream().collect(Collectors.toMap(CommandAudit::getCommandType, c -> 1L, Long::sum)); + } } diff --git a/src/main/java/net/ripe/rpki/core/read/services/ca/CertificateAuthorityViewServiceImpl.java b/src/main/java/net/ripe/rpki/core/read/services/ca/CertificateAuthorityViewServiceImpl.java index 764b758..0ec3721 100644 --- a/src/main/java/net/ripe/rpki/core/read/services/ca/CertificateAuthorityViewServiceImpl.java +++ b/src/main/java/net/ripe/rpki/core/read/services/ca/CertificateAuthorityViewServiceImpl.java @@ -7,6 +7,7 @@ import net.ripe.rpki.ripencc.provisioning.ProvisioningAuditLogService; import net.ripe.rpki.server.api.dto.*; import net.ripe.rpki.server.api.services.read.CertificateAuthorityViewService; +import org.apache.commons.lang3.tuple.Pair; import org.joda.time.Instant; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -148,7 +149,7 @@ public Collection getCaStatEvents() { } @Override - public Map findNonHostedPublisherRepositories(X500Principal caName) { + public Map> findNonHostedPublisherRepositories(X500Principal caName) { NonHostedCertificateAuthority ca = certificateAuthorityRepository.findByTypeAndName(NonHostedCertificateAuthority.class, caName); if (ca == null) { throw new EntityNotFoundException("non-hosted CA '" + caName + "' not found"); @@ -156,7 +157,7 @@ public Map findNonHostedPublisherRepositories(X500Prin return ca.getPublisherRepositories().values().stream().collect(Collectors.toMap( NonHostedPublisherRepository::getPublisherHandle, - NonHostedPublisherRepository::getRepositoryResponse + repository -> Pair.of(repository.getPublisherRequest(), repository.getRepositoryResponse()) )); } diff --git a/src/main/java/net/ripe/rpki/domain/DownStreamProvisioningCommunicator.java b/src/main/java/net/ripe/rpki/domain/DownStreamProvisioningCommunicator.java index 3d6ff0e..54d33ec 100644 --- a/src/main/java/net/ripe/rpki/domain/DownStreamProvisioningCommunicator.java +++ b/src/main/java/net/ripe/rpki/domain/DownStreamProvisioningCommunicator.java @@ -65,8 +65,7 @@ private X509Crl createProvisioningIdentityCrl(ProvisioningIdentityCertificate id X509CrlBuilder crlBuilder = new X509CrlBuilder(); crlBuilder.withIssuerDN(identityCertificate.getSubject()); - crlBuilder.withThisUpdateTime(identityCertificate.getValidityPeriod().getNotValidBefore()); - crlBuilder.withNextUpdateTime(identityCertificate.getValidityPeriod().getNotValidAfter()); + crlBuilder.withValidityPeriod(identityCertificate.getValidityPeriod()); crlBuilder.withNumber(BigInteger.ONE); crlBuilder.withAuthorityKeyIdentifier(persistedKeyPair.getPublicKey()); diff --git a/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfiguration.java b/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfiguration.java index 18f9d25..e801a87 100644 --- a/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfiguration.java +++ b/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfiguration.java @@ -1,6 +1,7 @@ package net.ripe.rpki.domain.alerts; import com.google.common.collect.Sets; +import lombok.Getter; import net.ripe.rpki.commons.validation.roa.AnnouncedRoute; import net.ripe.rpki.commons.validation.roa.RouteValidityState; import net.ripe.rpki.domain.CertificateAuthority; @@ -44,6 +45,7 @@ public class RoaAlertConfiguration extends EntitySupport { @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "roa_alert_conf_seq") private Long id; + @Getter @OneToOne(optional=false, fetch=FetchType.EAGER) @JoinColumn(name = "certificateauthority_id") private CertificateAuthority certificateAuthority; @@ -51,6 +53,7 @@ public class RoaAlertConfiguration extends EntitySupport { @Basic(optional=false) private String email; // May be empty for no subscription. + @Getter @Basic(optional=false) @Column(name = "frequency") @Enumerated(EnumType.STRING) @@ -85,14 +88,6 @@ public Object getId() { return id; } - public RoaAlertFrequency getFrequency() { - return frequency; - } - - public CertificateAuthority getCertificateAuthority() { - return certificateAuthority; - } - public void clearSubscription() { email = ""; routeValidityStates = ""; diff --git a/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfigurationRepository.java b/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfigurationRepository.java index 41a89f1..0a2fe3c 100644 --- a/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfigurationRepository.java +++ b/src/main/java/net/ripe/rpki/domain/alerts/RoaAlertConfigurationRepository.java @@ -14,4 +14,6 @@ public interface RoaAlertConfigurationRepository { RoaAlertConfiguration findByCertificateAuthorityIdOrNull(long caId); void remove(RoaAlertConfiguration entity); + + List findByEmail(String email); } diff --git a/src/main/java/net/ripe/rpki/domain/audit/CommandAuditService.java b/src/main/java/net/ripe/rpki/domain/audit/CommandAuditService.java index 3ba975e..17860ce 100644 --- a/src/main/java/net/ripe/rpki/domain/audit/CommandAuditService.java +++ b/src/main/java/net/ripe/rpki/domain/audit/CommandAuditService.java @@ -7,6 +7,7 @@ import net.ripe.rpki.server.api.dto.CommandAuditData; import java.util.List; +import java.util.Map; public interface CommandAuditService { @@ -25,4 +26,6 @@ public interface CommandAuditService { * Used when we delete a CA completely. */ void deleteCommandsForCa(long caId); + + Map findMentionsInSummary(String email); } diff --git a/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java b/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java index bca65f6..c25b4b8 100644 --- a/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java +++ b/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java @@ -130,8 +130,7 @@ public void update(ValidityPeriod validityPeriod, ResourceCertificateRepository X509CrlBuilder builder = newCrlBuilderWithEntries(revokedCertificates); builder.withAuthorityKeyIdentifier(keyPair.getPublicKey()); builder.withIssuerDN(keyPair.getCurrentIncomingCertificate().getSubject()); - builder.withThisUpdateTime(validityPeriod.getNotValidBefore()); - builder.withNextUpdateTime(validityPeriod.getNotValidAfter()); + builder.withValidityPeriod(validityPeriod); builder.withNumber(BigInteger.valueOf(getAndIncrementNextNumber())); builder.withSignatureProvider(keyPair.getSignatureProvider()); diff --git a/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java b/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java index 799e19c..36d89ad 100644 --- a/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java +++ b/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java @@ -174,8 +174,7 @@ private ManifestCms buildManifestCms(Collection manifestEntries } builder.withCertificate(certificate.getCertificate()); builder.withManifestNumber(BigInteger.valueOf(nextNumber)); - builder.withThisUpdateTime(certificate.getNotValidBefore()); - builder.withNextUpdateTime(certificate.getNotValidAfter()); + builder.withValidityPeriod(certificate.getValidityPeriod()); builder.withSignatureProvider(signatureProvider); return builder.build(eeKeyPair.getPrivate()); } diff --git a/src/main/java/net/ripe/rpki/rest/service/GdprService.java b/src/main/java/net/ripe/rpki/rest/service/GdprService.java new file mode 100644 index 0000000..fb8ee9f --- /dev/null +++ b/src/main/java/net/ripe/rpki/rest/service/GdprService.java @@ -0,0 +1,118 @@ +package net.ripe.rpki.rest.service; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import net.ripe.rpki.domain.alerts.RoaAlertConfigurationRepository; +import net.ripe.rpki.domain.audit.CommandAuditService; +import net.ripe.rpki.server.api.dto.RoaAlertSubscriptionData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.ws.rs.core.MediaType; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +@Scope("prototype") +@RestController +@RequestMapping(path = "/api/public/gdpr", produces = MediaType.APPLICATION_JSON) +@Tag(name = "/api/public/gdpr", description = "Return personal data according to GDPR") +public class GdprService { + private final CommandAuditService commandAuditService; + private final RoaAlertConfigurationRepository roaAlertConfigurationRepository; + + @Autowired + public GdprService(CommandAuditService commandAuditService, + RoaAlertConfigurationRepository roaAlertConfigurationRepository) { + this.commandAuditService = commandAuditService; + this.roaAlertConfigurationRepository = roaAlertConfigurationRepository; + } + + @PostMapping("/investigate") + @Operation(summary = "Search if one or more email addresses are present in RPKI core. Endpoint called by Controlroom.") + public GdprInvestigationResult investigate(@RequestBody GdprRequest req) { + var subscriptionEmails = new HashMap>(); + var reports = new ArrayList(); + var partOfRegistry = new AtomicBoolean(false); + + req.emails.stream().distinct().forEach(email -> { + roaAlertConfigurationRepository.findByEmail(email).forEach(rac -> { + RoaAlertSubscriptionData subscriptionOrNull = rac.getSubscriptionOrNull(); + if (subscriptionOrNull != null) { + subscriptionOrNull.getEmails().forEach(email1 -> + subscriptionEmails.compute(email1, (e, caNames) -> { + if (caNames == null) { + caNames = new ArrayList<>(1); + } + caNames.add(rac.getCertificateAuthority().getName().getName()); + return caNames; + })); + } + }); + + var caNames = subscriptionEmails.get(email); + if (caNames != null) { + var cas = String.join(", ", caNames); + reports.add(new GdprReport("Subscription", + "Subscribed '" + email + "' for alerts for the CA(s) " + cas, (long) caNames.size())); + } + + Map mentionsInSummary = commandAuditService.findMentionsInSummary(email); + if (!mentionsInSummary.isEmpty()) { + partOfRegistry.set(true); + } + mentionsInSummary.forEach((commandType, mentionCount) -> + reports.add(new GdprReport( + commandType, + "'" + email + "' found in the history of commands of type " + commandType, + mentionCount))); + }); + + if (req.id != null) { + Map mentionsInSummary = commandAuditService.findMentionsInSummary(req.id.toString()); + if (!mentionsInSummary.isEmpty()) { + partOfRegistry.set(true); + } + mentionsInSummary.forEach((commandType, mentionCount) -> + reports.add(new GdprReport( + commandType, + "'" + req.id + "' found in the history of commands of type " + commandType, + mentionCount))); + } + + return new GdprInvestigationResult( + reports, + reports.stream().anyMatch(r -> r.getOccurrences() > 0), + partOfRegistry.get()); + } + + @Builder + @Getter + public static class GdprReport { + private final String name; + private final String description; + private final Long occurrences; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class GdprRequest { + private UUID id; + private List emails; + } + + @Builder + @Getter + public static class GdprInvestigationResult { + private List reports; + private Boolean anyMatch; + private Boolean partOfRegistry; + } +} diff --git a/src/main/java/net/ripe/rpki/rest/service/PublisherRepositoriesService.java b/src/main/java/net/ripe/rpki/rest/service/PublisherRepositoriesService.java index 6c932bf..0aff3f3 100644 --- a/src/main/java/net/ripe/rpki/rest/service/PublisherRepositoriesService.java +++ b/src/main/java/net/ripe/rpki/rest/service/PublisherRepositoriesService.java @@ -1,5 +1,6 @@ package net.ripe.rpki.rest.service; +import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.Value; @@ -23,6 +24,8 @@ import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -63,6 +66,29 @@ public class PublisherRepositoriesService extends AbstractCaRestService { private final CommandService commandService; private final Optional maybeNonHostedPublisherRepositoryService; + /** + * A workaround for the long-standing issue https://github.com/NLnetLabs/krill/issues/984 that appears to be a wont-fix. + * + * @return repository response that is patched + */ + private static RepositoryResponse patchPublisherResponseTag(PublisherRequest publisherRequest, RepositoryResponse repositoryResponse) { + // krill does not handle tags correctly - copy this into the response. + // + // > tag: If the message included a "tag" attribute, + // > the repository MUST include an identical "tag" attribute in the + // > message; if the request did not include a + // > tag attribute, the response MUST NOT include a tag attribute + // > either. + return new RepositoryResponse( + publisherRequest.getTag(), + repositoryResponse.getServiceUri(), + repositoryResponse.getPublisherHandle(), + repositoryResponse.getSiaBase(), + repositoryResponse.getRrdpNotificationUri(), + repositoryResponse.getRepositoryBpkiTa() + ); + } + @Autowired public PublisherRepositoriesService(CertificateAuthorityViewService certificateAuthorityViewService, @@ -87,7 +113,7 @@ public ResponseEntity listNonHostedPublicationRepositories(@PathVariable("caN .findNonHostedPublisherRepositories(caName.getPrincipal()) .entrySet() .stream() - .collect(Collectors.toMap(Map.Entry::getKey, entry -> RepositoryResponseDto.of(entry.getValue()))); + .collect(Collectors.toMap(Map.Entry::getKey, entry -> RepositoryResponseDto.of(patchPublisherResponseTag(entry.getValue().getKey(), entry.getValue().getValue())))); return ResponseEntity.ok().body(Map.of("available", true, "repositories", repositories)); } catch (EntityNotFoundException e) { @@ -163,17 +189,17 @@ public ResponseEntity downloadNonHostedPublicationRepositoryResponse( log.info("Download repository non-hosted publication response for CA: {}", caName); try { - RepositoryResponse repositoryResponse = certificateAuthorityViewService + var publisherExchange = certificateAuthorityViewService .findNonHostedPublisherRepositories(caName.getPrincipal()) .get(publisherHandle); - if (repositoryResponse == null) { + if (publisherExchange == null) { throw new ObjectNotFoundException("publisher repository not found for handle '" + publisherHandle + "'"); } String filename = "repository-response-" + publisherHandle + ".xml"; - String xml = new RepositoryResponseSerializer().serialize(repositoryResponse); + String xml = new RepositoryResponseSerializer().serialize(patchPublisherResponseTag(publisherExchange.getKey(), publisherExchange.getValue())); return ResponseEntity.ok() - .header("content-disposition", "attachment; filename = " + filename) + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(filename).build().toString()) .contentType(TEXT_XML) .body(xml.getBytes(StandardCharsets.UTF_8)); } catch (EntityNotFoundException e) { @@ -208,6 +234,7 @@ public ResponseEntity deleteNonHostedPublicationRepository( @Value private static class RepositoryResponseDto { + @JsonInclude(JsonInclude.Include.NON_NULL) String tag; String serviceUri; String publisherHandle; diff --git a/src/main/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessor.java b/src/main/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessor.java index e842a7b..c5701d9 100644 --- a/src/main/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessor.java +++ b/src/main/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessor.java @@ -1,6 +1,7 @@ package net.ripe.rpki.ripencc.provisioning; import com.google.common.collect.Iterators; +import lombok.extern.slf4j.Slf4j; import net.ripe.ipresource.ImmutableResourceSet; import net.ripe.ipresource.IpResourceSet; import net.ripe.rpki.commons.crypto.x509cert.X509CertificateInformationAccessDescriptor; @@ -28,6 +29,7 @@ import org.springframework.stereotype.Component; import javax.inject.Inject; +import javax.validation.constraints.Null; import java.math.BigInteger; import java.net.URI; import java.security.PublicKey; @@ -40,6 +42,7 @@ import static net.ripe.rpki.domain.Resources.DEFAULT_RESOURCE_CLASS; +@Slf4j @Component class CertificateIssuanceProcessor extends AbstractProvisioningProcessor { @@ -229,7 +232,9 @@ private RpkiCaCertificateRequestParser parseCertificateRequest(CertificateIssuan try { PKCS10CertificationRequest pkc10Request = requestElement.getCertificateRequest(); return new RpkiCaCertificateRequestParser(pkc10Request); - } catch (RpkiCaCertificateRequestParserException e) { + // TODO: NPE can be removed after rpki-commons 1.38/2.0.0 is removed. + } catch (NullPointerException | RpkiCaCertificateRequestParserException e) { + log.error("Failed to parse certificate request", e); throw new NotPerformedException(NotPerformedError.REQ_BADLY_FORMED_CERTIFICATE_REQUEST); } } diff --git a/src/main/java/net/ripe/rpki/server/api/commands/SubscribeToRoaAlertCommand.java b/src/main/java/net/ripe/rpki/server/api/commands/SubscribeToRoaAlertCommand.java index 79cd9d0..bd0abcf 100644 --- a/src/main/java/net/ripe/rpki/server/api/commands/SubscribeToRoaAlertCommand.java +++ b/src/main/java/net/ripe/rpki/server/api/commands/SubscribeToRoaAlertCommand.java @@ -1,5 +1,6 @@ package net.ripe.rpki.server.api.commands; +import lombok.Getter; import net.ripe.rpki.commons.util.VersionedId; import net.ripe.rpki.commons.validation.roa.RouteValidityState; import net.ripe.rpki.domain.alerts.RoaAlertFrequency; @@ -12,6 +13,7 @@ * Subscribe an email address to alerts about BGP updates seen by RIS * that are invalidated by the CA's ROAs. */ +@Getter public class SubscribeToRoaAlertCommand extends CertificateAuthorityCommand { private final String email; @@ -34,18 +36,6 @@ public SubscribeToRoaAlertCommand(VersionedId certificateAuthorityId, String ema this.frequency = frequency; } - public String getEmail() { - return email; - } - - public Collection getRouteValidityStates() { - return routeValidityStates; - } - - public RoaAlertFrequency getFrequency() { - return frequency; - } - // Let's make this conform to human repre private String validitySummary(){ if(routeValidityStates.contains(RouteValidityState.UNKNOWN)) diff --git a/src/main/java/net/ripe/rpki/server/api/commands/UnsubscribeFromRoaAlertCommand.java b/src/main/java/net/ripe/rpki/server/api/commands/UnsubscribeFromRoaAlertCommand.java index 6339207..f2a728e 100644 --- a/src/main/java/net/ripe/rpki/server/api/commands/UnsubscribeFromRoaAlertCommand.java +++ b/src/main/java/net/ripe/rpki/server/api/commands/UnsubscribeFromRoaAlertCommand.java @@ -1,5 +1,6 @@ package net.ripe.rpki.server.api.commands; +import lombok.Getter; import net.ripe.rpki.commons.util.VersionedId; /** @@ -8,6 +9,7 @@ */ public class UnsubscribeFromRoaAlertCommand extends CertificateAuthorityCommand { + @Getter private final String email; public UnsubscribeFromRoaAlertCommand(VersionedId certificateAuthorityId, String email) { @@ -15,10 +17,6 @@ public UnsubscribeFromRoaAlertCommand(VersionedId certificateAuthorityId, String this.email = email; } - public String getEmail() { - return email; - } - @Override public String getCommandSummary() { return "Unsubscribed " + email + " from ROA alerts."; diff --git a/src/main/java/net/ripe/rpki/server/api/services/read/CertificateAuthorityViewService.java b/src/main/java/net/ripe/rpki/server/api/services/read/CertificateAuthorityViewService.java index f3a1871..0914724 100644 --- a/src/main/java/net/ripe/rpki/server/api/services/read/CertificateAuthorityViewService.java +++ b/src/main/java/net/ripe/rpki/server/api/services/read/CertificateAuthorityViewService.java @@ -5,6 +5,7 @@ import net.ripe.rpki.domain.CertificateAuthority; import net.ripe.rpki.domain.ManagedCertificateAuthority; import net.ripe.rpki.server.api.dto.*; +import org.apache.commons.lang3.tuple.Pair; import org.joda.time.Instant; import javax.security.auth.x500.X500Principal; @@ -71,7 +72,7 @@ Collection findManagedCasEligibleForKeyRoll( Collection getCaStatEvents(); - Map findNonHostedPublisherRepositories(X500Principal caName); + Map> findNonHostedPublisherRepositories(X500Principal caName); Map findAllPublisherRequestsFromNonHostedCAs(); diff --git a/src/main/java/net/ripe/rpki/services/impl/jpa/JpaRoaAlertConfigurationRepository.java b/src/main/java/net/ripe/rpki/services/impl/jpa/JpaRoaAlertConfigurationRepository.java index f159b2d..bb23b05 100644 --- a/src/main/java/net/ripe/rpki/services/impl/jpa/JpaRoaAlertConfigurationRepository.java +++ b/src/main/java/net/ripe/rpki/services/impl/jpa/JpaRoaAlertConfigurationRepository.java @@ -36,4 +36,12 @@ public List findByFrequency(RoaAlertFrequency frequency) query.setParameter("frequency", frequency); return query.getResultList(); } + + @SuppressWarnings("unchecked") + @Override + public List findByEmail(String email) { + Query query = createQuery("SELECT rac FROM RoaAlertConfiguration rac WHERE email LIKE :email"); + query.setParameter("email", "%" + email + "%"); + return query.getResultList(); + } } diff --git a/src/test/java/net/ripe/rpki/application/impl/CommandAuditServiceBeanTest.java b/src/test/java/net/ripe/rpki/application/impl/CommandAuditServiceBeanTest.java index fa7fd9e..5dd8b29 100644 --- a/src/test/java/net/ripe/rpki/application/impl/CommandAuditServiceBeanTest.java +++ b/src/test/java/net/ripe/rpki/application/impl/CommandAuditServiceBeanTest.java @@ -1,5 +1,6 @@ package net.ripe.rpki.application.impl; +import net.ripe.rpki.commons.validation.roa.RouteValidityState; import net.ripe.rpki.domain.CertificationDomainTestCase; import net.ripe.rpki.domain.ManagedCertificateAuthority; import net.ripe.rpki.domain.audit.CommandAudit; @@ -13,6 +14,8 @@ import javax.inject.Inject; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.UUID; import static net.ripe.rpki.server.api.commands.CertificateAuthorityCommandGroup.USER; @@ -26,10 +29,10 @@ public class CommandAuditServiceBeanTest extends CertificationDomainTestCase { private static final UUID TEST_USER_UUID = UUID.randomUUID(); private ManagedCertificateAuthority ca; + @Before public void setUp() { clearDatabase(); - ca = createInitialisedProdCaWithRipeResources(); RunAsUserHolder.set(RunAsUser.operator(TEST_USER_UUID)); } @@ -87,4 +90,24 @@ public void should_set_deleted_at_for_all_commands_including_current_command() { entityManager.refresh(commandContext.getCommandAudit()); assertThat(commandContext.getCommandAudit().getDeletedAt()).isNotNull(); } + + @Test + public void should_extract_emails_from_subscribe_command_summary() { + String email = "user_some.email+ripe.net@gmail.com"; + + recordCommand(new SubscribeToRoaAlertCommand(ca.getVersionedId(), email, List.of(RouteValidityState.INVALID_ASN)), "some event"); + recordCommand(new UnsubscribeFromRoaAlertCommand(ca.getVersionedId(), email), "some event 2"); + + entityManager.flush(); + Map emailMentions = subject.findMentionsInSummary(email); + assertThat(emailMentions.size()).isEqualTo(2); + assertThat(emailMentions.get("SubscribeToRoaAlertCommand")).isEqualTo(1L); + assertThat(emailMentions.get("UnsubscribeFromRoaAlertCommand")).isEqualTo(1L); + } + + private void recordCommand(CertificateAuthorityCommand command, String event) { + CommandContext commandContext = subject.startRecording(command); + commandContext.recordEvent(event); + subject.finishRecording(commandContext); + } } diff --git a/src/test/java/net/ripe/rpki/rest/service/GdprServiceTest.java b/src/test/java/net/ripe/rpki/rest/service/GdprServiceTest.java new file mode 100644 index 0000000..001c016 --- /dev/null +++ b/src/test/java/net/ripe/rpki/rest/service/GdprServiceTest.java @@ -0,0 +1,110 @@ +package net.ripe.rpki.rest.service; + +import net.ripe.rpki.TestRpkiBootApplication; +import net.ripe.rpki.application.impl.CommandAuditServiceBean; +import net.ripe.rpki.commons.validation.roa.RouteValidityState; +import net.ripe.rpki.domain.CertificateAuthority; +import net.ripe.rpki.domain.alerts.RoaAlertConfiguration; +import net.ripe.rpki.domain.alerts.RoaAlertConfigurationRepository; +import net.ripe.rpki.domain.alerts.RoaAlertFrequency; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import javax.security.auth.x500.X500Principal; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ActiveProfiles("test") +@RunWith(SpringRunner.class) +@AutoConfigureMockMvc +@AutoConfigureWebMvc +@SpringBootTest(classes = TestRpkiBootApplication.class) +public class GdprServiceTest { + + @MockBean + private CommandAuditServiceBean commandAuditService; + + @MockBean + private RoaAlertConfigurationRepository roaAlertConfigurationRepository; + + @Autowired + private MockMvc mockMvc; + + @Test + public void shouldReturnEmptyResult() throws Exception { + mockMvc.perform(Rest.post("/api/public/gdpr/investigate") + .content("{\"emails\":[\"bad@ripe.net\",\"bad2@ripe.net\",\"bad@ripe.net\"],\"id\":\"cef5372c-ac38-4bde-863d-2e7b5b44e8c0\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value("3")) + .andExpect(jsonPath("$.reports.length()").value("0")) + .andExpect(jsonPath("$.anyMatch").value("false")); + } + + @Test + public void shouldReturnSubscribedEmail() throws Exception { + var caName1 = "CN=12345"; + var ca1 = mock(CertificateAuthority.class); + when(ca1.getName()).thenReturn(new X500Principal(caName1)); + + var alert1 = new RoaAlertConfiguration(ca1, "bad@ripe.net", + Collections.singletonList(RouteValidityState.INVALID_ASN), RoaAlertFrequency.DAILY); + var alert2 = new RoaAlertConfiguration(ca1, "bad2@ripe.net", + Collections.singletonList(RouteValidityState.INVALID_ASN), RoaAlertFrequency.DAILY); + + when(roaAlertConfigurationRepository.findByEmail("bad@ripe.net")).thenReturn(List.of(alert1, alert2)); + + mockMvc.perform(Rest.post("/api/public/gdpr/investigate") + .content("{\"emails\":[\"bad@ripe.net\",\"bad2@ripe.net\",\"bad@ripe.net\"],\"id\":\"cef5372c-ac38-4bde-863d-2e7b5b44e8c0\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.reports.length()").value("2")) + .andExpect(jsonPath("$.reports.[0].name").value("Subscription")) + .andExpect(jsonPath("$.reports.[0].description").value("Subscribed 'bad@ripe.net' for alerts for the CA(s) " + caName1)) + .andExpect(jsonPath("$.reports.[0].occurrences").value("1")) + .andExpect(jsonPath("$.reports.[1].name").value("Subscription")) + .andExpect(jsonPath("$.reports.[1].description").value("Subscribed 'bad2@ripe.net' for alerts for the CA(s) " + caName1)) + .andExpect(jsonPath("$.reports.[1].occurrences").value("1")) + .andExpect(jsonPath("$.anyMatch").value("true")) + .andExpect(jsonPath("$.partOfRegistry").value("false")); + } + + @Test + public void shouldReturnHistoryEmail() throws Exception { + Map history = new HashMap<>(); + history.put("RoaAlertConfiguration", 1L); + history.put("SubscribeToRoaAlertCommand", 2L); + history.put("UnsubscribeFromRoaAlertCommand", 3L); + when(commandAuditService.findMentionsInSummary("bad@ripe.net")).thenReturn(history); + + mockMvc.perform(Rest.post("/api/public/gdpr/investigate") + .content("{\"emails\":[\"bad@ripe.net\",\"bad2@ripe.net\",\"bad@ripe.net\"],\"id\":\"cef5372c-ac38-4bde-863d-2e7b5b44e8c0\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.reports.length()").value("3")) + .andExpect(jsonPath("$.reports.[0].name").value("UnsubscribeFromRoaAlertCommand")) + .andExpect(jsonPath("$.reports.[0].description").value("'bad@ripe.net' found in the history of commands of type UnsubscribeFromRoaAlertCommand")) + .andExpect(jsonPath("$.reports.[0].occurrences").value("3")) + .andExpect(jsonPath("$.reports.[1].name").value("SubscribeToRoaAlertCommand")) + .andExpect(jsonPath("$.reports.[1].description").value("'bad@ripe.net' found in the history of commands of type SubscribeToRoaAlertCommand")) + .andExpect(jsonPath("$.reports.[1].occurrences").value("2")) + .andExpect(jsonPath("$.reports.[2].name").value("RoaAlertConfiguration")) + .andExpect(jsonPath("$.reports.[2].description").value("'bad@ripe.net' found in the history of commands of type RoaAlertConfiguration")) + .andExpect(jsonPath("$.reports.[2].occurrences").value("1")) + .andExpect(jsonPath("$.anyMatch").value("true")) + .andExpect(jsonPath("$.partOfRegistry").value("true")); + } + +} \ No newline at end of file diff --git a/src/test/java/net/ripe/rpki/rest/service/PublisherRepositoriesServiceTest.java b/src/test/java/net/ripe/rpki/rest/service/PublisherRepositoriesServiceTest.java index 1cf4d22..7ad64bd 100644 --- a/src/test/java/net/ripe/rpki/rest/service/PublisherRepositoriesServiceTest.java +++ b/src/test/java/net/ripe/rpki/rest/service/PublisherRepositoriesServiceTest.java @@ -2,7 +2,10 @@ import net.ripe.ipresource.ImmutableResourceSet; import net.ripe.rpki.TestRpkiBootApplication; +import net.ripe.rpki.commons.provisioning.identity.PublisherRequest; +import net.ripe.rpki.commons.provisioning.identity.PublisherRequestSerializer; import net.ripe.rpki.commons.provisioning.identity.RepositoryResponse; +import net.ripe.rpki.commons.provisioning.identity.RepositoryResponseSerializer; import net.ripe.rpki.commons.provisioning.x509.ProvisioningIdentityCertificateBuilderTest; import net.ripe.rpki.commons.util.VersionedId; import net.ripe.rpki.rest.exception.CaNameInvalidException; @@ -13,7 +16,11 @@ import net.ripe.rpki.server.api.services.command.CommandService; import net.ripe.rpki.server.api.services.read.CertificateAuthorityViewService; import net.ripe.rpki.server.api.support.objects.CaName; -import org.junit.Test; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -23,12 +30,14 @@ import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import javax.persistence.EntityNotFoundException; import javax.security.auth.x500.X500Principal; +import javax.ws.rs.core.MediaType; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -37,15 +46,19 @@ import static net.ripe.rpki.rest.service.AbstractCaRestService.API_URL_PREFIX; import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasSize; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.web.servlet.function.RequestPredicates.contentType; @ActiveProfiles("test") -@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) @AutoConfigureMockMvc @AutoConfigureWebMvc @SpringBootTest(classes = TestRpkiBootApplication.class) @@ -92,6 +105,29 @@ public class PublisherRepositoriesServiceTest { "1BjcmhYHqott+cnK1ITOjLe9EKejRZv/7/BFsmpzm2Zbq1KA\n" + "\n" + ""; + private static final String PUBLISHER_REQUEST_TAG_XML = + "\n" + + "\n" + + "MIIDIDCCAgigAwIBAgIBATANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDEx5Cb2Ig\n" + + "QlBLSSBSZXNvdXJjZSBUcnVzdCBBbmNob3IwHhcNMTEwNzAxMDQwNzIzWhcNMTIw\n" + + "NjMwMDQwNzIzWjApMScwJQYDVQQDEx5Cb2IgQlBLSSBSZXNvdXJjZSBUcnVzdCBB\n" + + "bmNob3IwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEk1f7cVzHu3r/\n" + + "fJ5gkBxnWMNJ1CP0kPtfP8oFOEVVH1lX0MHuFKhdKA4WCkGkeFtGb/4T/nGgsD+z\n" + + "exZid6RR8zjHkwMLvAl0x6wdKa46XJbFu+wTSu+vlowVY9rGzH+ttv4Fj6E2Y3DG\n" + + "983/dVNJfXl00+Ts7rlvVcn9lI5dWvzsLoUOdhD4hsyKp53k8i4HexiD+0ugPeh9\n" + + "4PKiyZOuMjSRNQSBUA3ElqJSRZz7nWvs/j6zhwHdFa+lN56575Mc5mrwr+KePwW5\n" + + "DLt3izYpjwKffVuxUKPTrhvnOOg5kBBv0ihync21LSLds6jusxaMYUwUElO8KQyn\n" + + "NUAeGPd/AgMBAAGjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFORFOC3G\n" + + "PjYKn7V1/BJHDmZ4W7J+MB8GA1UdIwQYMBaAFORFOC3GPjYKn7V1/BJHDmZ4W7J+\n" + + "MA0GCSqGSIb3DQEBCwUAA4IBAQBqsP4ENtWTkNdsekYB+4hO/Afq20Ho0W8lyTkM\n" + + "JO1UFDt/dzFAmTT4uM7pmwuQfqmCYjNDWon8nsdFno4tA0is3aiq6yIMAYzBE5ub\n" + + "bnJMxldqLoWuakco1wYa3kZFzWPwecxgJ4ZlqTPGu0Loyjibt25IE9MfixyWDw+D\n" + + "MhyfonLLgFb5jz7A3BTE63vlTp359uDbFb1nRdyoT31s3FUBK8jF4B5pWzPiLdct\n" + + "bOMVjYUBs8aFC3fDXyGSr/RcjE4OOZQyTkYZn8zCPUJ4KqOPAUV9u9jx2FPvOcA3\n" + + "1BjcmhYHqott+cnK1ITOjLe9EKejRZv/7/BFsmpzm2Zbq1KA\n" + + "\n" + + ""; + @Test public void should_provision_non_hosted_publisher_repository() throws Exception { @@ -151,26 +187,39 @@ public void should_reject_creating_too_many_nonhosted_publisher_repositories() t assertThat(response.getContentAsString()).contains("error", "limit exceeded"); } - @Test - public void should_list_non_hosted_publisher_repositories() throws Exception { + @ParameterizedTest + @ValueSource(strings = {PUBLISHER_REQUEST_XML, PUBLISHER_REQUEST_TAG_XML}) + public void should_list_non_hosted_publisher_repositories(String publisherRequestXml) throws Exception { + var publisherRequest = new PublisherRequestSerializer().deserialize(publisherRequestXml); + UUID publisherHandle = UUID.randomUUID(); when(certificateAuthorityViewService.findCertificateAuthorityByName(NON_HOSTED_CA_DATA.getName())) .thenReturn(NON_HOSTED_CA_DATA); when(certificateAuthorityViewService.findNonHostedPublisherRepositories(NON_HOSTED_CA_DATA.getName())) - .thenReturn(Collections.singletonMap(publisherHandle, new RepositoryResponse( - Optional.empty(), - URI.create("https://rpki.example.com/"), - publisherHandle.toString(), - URI.create("rsync://rpki.example.com/repo/handle/"), - Optional.empty(), - ProvisioningIdentityCertificateBuilderTest.TEST_IDENTITY_CERT_2 - ))); - - MvcResult result = mockMvc.perform(Rest.get(API_URL_PREFIX + "/ORG-1/non-hosted/publisher-repositories")).andReturn(); - - final MockHttpServletResponse response = result.getResponse(); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentAsString()).contains("\"publisherHandle\":\"" + publisherHandle + "\""); + .thenReturn(Collections.singletonMap(publisherHandle, Pair.of( + publisherRequest, + new RepositoryResponse( + Optional.empty(), + URI.create("https://rpki.example.com/"), + publisherHandle.toString(), + URI.create("rsync://rpki.example.com/repo/handle/"), + Optional.empty(), + ProvisioningIdentityCertificateBuilderTest.TEST_IDENTITY_CERT_2 + ) + ))); + + var resp = mockMvc.perform(Rest.get(API_URL_PREFIX + "/ORG-1/non-hosted/publisher-repositories")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.repositories").isMap()) + .andExpect(jsonPath("$.repositories[*].publisherHandle").value(publisherHandle.toString())); + + var tag = publisherRequest.getTag(); + + if (tag.isPresent()) { + resp.andExpect(jsonPath("$.repositories[*].tag").value(tag.get())); + } else { + resp.andExpect(jsonPath("$.repositories[*].tag").doesNotExist()); + } } @Test @@ -185,26 +234,37 @@ public void should_handle_non_existing_ca_when_listing_non_hosted_publisher_repo assertThat(response.getContentAsString()).contains("error"); } - @Test - public void should_download_non_hosted_publisher_repository_response() throws Exception { + @ParameterizedTest + @ValueSource(strings = {PUBLISHER_REQUEST_XML, PUBLISHER_REQUEST_TAG_XML}) + public void should_download_non_hosted_publisher_repository_response(String publisherRequestXml) throws Exception { + var publisherRequest = new PublisherRequestSerializer().deserialize(publisherRequestXml); + UUID publisherHandle = UUID.randomUUID(); when(certificateAuthorityViewService.findCertificateAuthorityByName(NON_HOSTED_CA_DATA.getName())) .thenReturn(NON_HOSTED_CA_DATA); when(certificateAuthorityViewService.findNonHostedPublisherRepositories(NON_HOSTED_CA_DATA.getName())) - .thenReturn(Collections.singletonMap(publisherHandle, new RepositoryResponse( - Optional.empty(), - URI.create("https://rpki.example.com/"), - publisherHandle.toString(), - URI.create("rsync://rpki.example.com/repo/handle/"), - Optional.empty(), - ProvisioningIdentityCertificateBuilderTest.TEST_IDENTITY_CERT_2 + .thenReturn(Collections.singletonMap(publisherHandle, Pair.of( + publisherRequest, + new RepositoryResponse( + Optional.empty(), + URI.create("https://rpki.example.com/"), + publisherHandle.toString(), + URI.create("rsync://rpki.example.com/repo/handle/"), + Optional.empty(), + ProvisioningIdentityCertificateBuilderTest.TEST_IDENTITY_CERT_2 + ) ))); MvcResult result = mockMvc.perform(Rest.get(API_URL_PREFIX + "/ORG-1/non-hosted/publisher-repositories/" + publisherHandle + "/repository-response")).andReturn(); final MockHttpServletResponse response = result.getResponse(); + assertThat(response.getContentType()).isEqualTo(MediaType.TEXT_XML); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - assertThat(response.getContentAsString()).contains("publisher_handle=\"" + publisherHandle + "\""); + + // Parse the response and validate that the tag matches + var publisherResponse = new RepositoryResponseSerializer().deserialize(response.getContentAsString()); + assertThat(publisherResponse.getTag()).isEqualTo(publisherRequest.getTag()); + assertThat(publisherResponse.getPublisherHandle()).isEqualTo(publisherHandle.toString()); } @Test diff --git a/src/test/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessorTest.java b/src/test/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessorTest.java index 2b103c1..cd5f024 100644 --- a/src/test/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessorTest.java +++ b/src/test/java/net/ripe/rpki/ripencc/provisioning/CertificateIssuanceProcessorTest.java @@ -22,6 +22,7 @@ import net.ripe.rpki.server.api.ports.ResourceLookupService; import net.ripe.rpki.server.api.services.command.CommandService; import net.ripe.rpki.server.api.services.read.ResourceCertificateViewService; +import org.assertj.core.api.Assertions; import org.bouncycastle.asn1.pkcs.CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.joda.time.DateTime; diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline