Skip to content

Commit

Permalink
RIPE NCC has merged 11a030be0
Browse files Browse the repository at this point in the history
* Update node Docker tag to v22 [42ee05bee]
* Update dependency org.eclipse.jgit:org.eclipse.jgit to v6 [447b855d2]
* Cleanup left-over 'authorization.admin.role' in profiles [da3e72785]
* Update dependency org.sonarqube:org.sonarqube.gradle.plugin to v5 [1adb4a140]
* Update dependency io.sentry:sentry-bom to v7 [d3a369f76]
* Update dependency commons-codec:commons-codec to v1.17.0 [df69caf44]
* Refactor Mac initialisation [f29622783]
* Update dependency com.gorylenko.gradle-git-properties:com.gorylenko.gradle-git-properties.gradle.plugin to v2.4.2 [6f8670965]
* More Sonarqube quirks [38a6f0e38]
* Sonarqube issues [b69e7e8bc]
* Better error messages [50baec128]
* Formatting [b8f2f3aa4]
* Make tokens URL-safe [8315ccd6b]
* Use proper hmac, higher entropy secrets [f8063063c]
* Include a unique id from the CA into the email unsubscribe token [76d67e240]
* Use global secret instead of per-configuration token [d790f1417]
* Include URL in RRDP duration tracker [d379c9d3c]
* DBProvider 1.6 [c1344ea79]
* Update dependency org.wiremock:wiremock-jetty12 to v3.5.4 [0fb6ccc99]
* Fix compilation [89122afc0]
* Fix migration numbers [11d0c758b]
* Add authentication-first URL [ea5f7806e]
* Revert error processing in the method [32f355f88]
* Syntax fix [c1dd5814d]
* Fix response building [c1d4c0959]
* Error handling [a37cf9900]
* Introduce separate alertUnsubscribeUri property [c7e8a1cbf]
* Use PathVariables like all the other code does [de7f52739]
* Fix NULL value [05db29b46]
* Fix tests [a9b551ad1]
* Delete unused method [2bd5eb3ca]
* Improve email template tests [e4aa937d7]
* Extend templates with unsubscribe URL [ebff2c8ec]
* Pass around unsubscribeToken [9885d1432]
* Cleanup smells [8be42f963]
* Cleanup imports [cac48e104]
* Refactor [5378dd72c]
* Add unsubscribeToken [cd583ee56]
* Formatting [5c9adc0d3]
* Add unsubscribeToken field [58278c3c9]
* Use commands to unsubscribe [005824705]
* Fix broken tests [185ac4e72]
* Add unsubscribe API end-point [0c89379ad]
  • Loading branch information
RPKI Team at RIPE NCC committed May 16, 2024
1 parent e1f4451 commit 5bcbdb4
Show file tree
Hide file tree
Showing 37 changed files with 464 additions and 212 deletions.
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ sonarqube:
- if: $CI_COMMIT_BRANCH == "next"

control/run-on-staging:
image: node:21-alpine
image: node:22-alpine
stage: qa
script:
- ./scripts/gitlab-deploy-check
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ spring.security.oauth2.client:
client-secret: '<github-client-secret>'
```
Also change the `authorization.admin.role` to `ROLE_USER`.
Also change the `admin.authorization.enabled` to `true`.

Make sure you do not check in your secrets!

Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ dependencies {
implementation "org.thymeleaf:thymeleaf:3.1.2.RELEASE"
implementation "org.thymeleaf:thymeleaf-spring6:3.1.2.RELEASE"

implementation platform('io.sentry:sentry-bom:6.34.0')
implementation platform('io.sentry:sentry-bom:7.9.0')
implementation 'io.sentry:sentry-spring-boot-starter'
implementation 'io.sentry:sentry-logback'

Expand All @@ -58,7 +58,7 @@ 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.1'
implementation 'commons-codec:commons-codec:1.17.0'
implementation 'commons-io:commons-io:2.16.1'
implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5'
implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5'
Expand All @@ -72,7 +72,7 @@ dependencies {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}

testImplementation "org.wiremock:wiremock-jetty12:3.5.2"
testImplementation "org.wiremock:wiremock-jetty12:3.5.4"
testImplementation 'net.jqwik:jqwik:1.8.4'
testImplementation "net.ripe.rpki:rpki-commons:$rpki_commons_version:tests"
testImplementation 'org.assertj:assertj-core'
Expand Down
6 changes: 3 additions & 3 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ repositories {

dependencies {
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') {
implementation('com.gorylenko.gradle-git-properties:com.gorylenko.gradle-git-properties.gradle.plugin:2.4.2') {
exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit'
}
implementation 'org.eclipse.jgit:org.eclipse.jgit:5.13.3.202401111512-r'
implementation 'org.sonarqube:org.sonarqube.gradle.plugin:4.4.1.3373'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.9.0.202403050737-r'
implementation 'org.sonarqube:org.sonarqube.gradle.plugin:5.0.0.4638'
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ repositories {
maven {
url = uri('https://maven.nexus.ripe.net/repository/maven-third-party')
}
maven {
url = uri('https://maven.nexus.ripe.net/repository/maven-third-party-snapshots')
}
// Use when testing new third party dependencies
// maven {
// url = uri('https://maven.nexus.ripe.net/repository/maven-third-party-snapshots')
// }
}

java {
Expand Down
4 changes: 2 additions & 2 deletions hsm/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ dependencies {
}
}
thalesImplementation "net.ripe.rpki:rpki-commons:$rpki_commons_version"
// 2024-4-16: Test DBProvider snapshot provided by Entrust
thalesImplementation 'com.thales.esecurity.asg.ripe.db-jceprovider:DBProvider:1.6-SNAPSHOT'
// 2024-4-26: Final DBProvider 1.6 provided by Entrust
thalesImplementation 'com.thales.esecurity.asg.ripe.db-jceprovider:DBProvider:1.6'
// **When using JDK 11** make sure the matching version of nCipherKM is on classpath because DBProvider depends on it.
thalesImplementation 'com.ncipher.nfast:nCipherKM:13.4.5'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@Entity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.UUID;


public interface RoaAlertConfigurationRepository {
Expand All @@ -16,4 +18,6 @@ public interface RoaAlertConfigurationRepository {
void remove(RoaAlertConfiguration entity);

List<RoaAlertConfiguration> findByEmail(String email);

Optional<RoaAlertConfiguration> findByUnsubscribeToken(UUID unsubscribeToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@ private Counter createCounter(MeterRegistry meterRegistry, String operation, Obj
.register(meterRegistry);
}

private static Timer createTimer(MeterRegistry meterRegistry, String status) {
private Timer createTimer(MeterRegistry meterRegistry, String status) {
return Timer.builder("rpkicore.publication.request.duration")
.tag("status", status)
.tag("uri", this.publishingServerUrl.toString())
.description("Time for publication HTTP request")
.publishPercentileHistogram()
.minimumExpectedValue(Duration.ofMillis(4))
Expand Down
78 changes: 78 additions & 0 deletions src/main/java/net/ripe/rpki/rest/service/EmailService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package net.ripe.rpki.rest.service;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.core.MediaType;
import lombok.extern.slf4j.Slf4j;
import net.ripe.rpki.domain.alerts.RoaAlertConfigurationRepository;
import net.ripe.rpki.server.api.commands.UnsubscribeFromRoaAlertCommand;
import net.ripe.rpki.server.api.dto.RoaAlertSubscriptionData;
import net.ripe.rpki.server.api.services.command.CommandService;
import net.ripe.rpki.services.impl.email.EmailTokens;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.springframework.http.HttpStatus.NOT_FOUND;

@Slf4j
@Scope("prototype")
@RestController
@RequestMapping(path = "/api/email", produces = MediaType.APPLICATION_JSON)
@Tag(name = "/api/email", description = "Manage email subscriptions")
public class EmailService extends RestService {

public static final String ERROR = "error";

private final CommandService commandService;
private final RoaAlertConfigurationRepository roaAlertConfigurationRepository;
private final EmailTokens emailTokens;

@Autowired
public EmailService(CommandService commandService,
EmailTokens emailTokens,
RoaAlertConfigurationRepository roaAlertConfigurationRepository) {
this.commandService = commandService;
this.roaAlertConfigurationRepository = roaAlertConfigurationRepository;
this.emailTokens = emailTokens;
}

@PostMapping("/unsubscribe/{email}/{token}")
@Operation(summary = "Implement one-click unsubscribe functionality.")
public ResponseEntity<?> unsubscribe(
@PathVariable("email") final String email,
@PathVariable("token") final String token) {

if (Strings.isBlank(token)) {
return ResponseEntity.status(NOT_FOUND).body(Map.of(ERROR, "Unknown or invalid token: " + token));
}
var configurations = roaAlertConfigurationRepository.findByEmail(email);
if (configurations.isEmpty()) {
return ResponseEntity.status(NOT_FOUND).body(Map.of(ERROR, "Unknown email " + email));
}
var unsubscribedAnyone = new AtomicBoolean(false);
configurations.forEach(configuration -> {
RoaAlertSubscriptionData subscriptionOrNull = configuration.getSubscriptionOrNull();
var ca = configuration.getCertificateAuthority();
var configurationToken = emailTokens.createUnsubscribeToken(EmailTokens.uniqueId(ca.getUuid()), email);
if (subscriptionOrNull != null
&& subscriptionOrNull.getEmails().contains(email)
&& token.equals(configurationToken)) {
commandService.execute(new UnsubscribeFromRoaAlertCommand(ca.getVersionedId(), email));
unsubscribedAnyone.set(true);
}
});
if (unsubscribedAnyone.get()) {
return ResponseEntity.ok().body(Map.of("success", "Unsubscribed " + email));
}
return ResponseEntity.status(NOT_FOUND).body(Map.of(ERROR, "Unknown token " + token));
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
package net.ripe.rpki.server.api.dto;

import lombok.Getter;
import net.ripe.rpki.commons.validation.roa.RouteValidityState;
import net.ripe.rpki.domain.alerts.RoaAlertFrequency;
import net.ripe.rpki.server.api.support.objects.ValueObjectSupport;

import java.util.*;


@Getter
public class RoaAlertSubscriptionData extends ValueObjectSupport {

private final List<String> emails;
private final EnumSet<RouteValidityState> routeValidityStates;
private final RoaAlertFrequency frequency;
private final EnumSet<RouteValidityState> routeValidityStates;

public RoaAlertSubscriptionData(String email, Collection<RouteValidityState> routeValidityStates, RoaAlertFrequency frequency) {
this.emails = new ArrayList<>();
emails.add(email);
this.routeValidityStates = EnumSet.copyOf(routeValidityStates);
this.frequency = frequency;
this(List.of(email), routeValidityStates, frequency);
}

public RoaAlertSubscriptionData(List<String> emails, Collection<RouteValidityState> routeValidityStates, RoaAlertFrequency frequency) {
public RoaAlertSubscriptionData(List<String> emails, Collection<RouteValidityState> routeValidityStates,
RoaAlertFrequency frequency) {
this.emails = new ArrayList<>(emails);
this.routeValidityStates = EnumSet.copyOf(routeValidityStates);
this.frequency = frequency;
}

public List<String> getEmails() {
return emails;
}

public RoaAlertFrequency getFrequency() {
return frequency;
}

public Set<RouteValidityState> getRouteValidityStates() {
return routeValidityStates;
}
}
84 changes: 0 additions & 84 deletions src/main/java/net/ripe/rpki/services/impl/EmailSenderBean.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import net.ripe.rpki.server.api.ports.InternalNamePresenter;
import net.ripe.rpki.server.api.services.read.BgpRisEntryViewService;
import net.ripe.rpki.server.api.services.read.RoaViewService;
import net.ripe.rpki.services.impl.email.EmailSender;
import net.ripe.rpki.services.impl.email.EmailTokens;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -152,7 +154,8 @@ private void sendRoaAlertEmailToSubscription(RoaAlertConfigurationData configura
email,
String.format(EmailSender.EmailTemplates.ROA_ALERT.templateSubject, humanizedCaName),
EmailSender.EmailTemplates.ROA_ALERT,
parameters)
parameters,
EmailTokens.uniqueId(configuration.getCertificateAuthority().getUuid()))
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
package net.ripe.rpki.services.impl;
package net.ripe.rpki.services.impl.email;

import java.util.Map;

public interface EmailSender {

void sendEmail(String emailTo, String subject, EmailTemplates template, Map<String, Object> parameters);
void sendEmail(String emailTo, String subject, EmailTemplates template, Map<String, Object> parameters, String uniqueId);

// Limit the number of possible inputs to allow us to check all templates in tests.
enum EmailTemplates {
ROA_ALERT_SUBSCRIBE_CONFIRMATION_WEEKLY("email-templates/subscribe-confirmation-weekly.txt", "Your Resource Certification (RPKI) alerts subscription"),
ROA_ALERT_SUBSCRIBE_CONFIRMATION_DAILY("email-templates/subscribe-confirmation-daily.txt", "Your Resource Certification (RPKI) alerts subscription"),
ROA_ALERT_UNSUBSCRIBE("email-templates/unsubscribe-confirmation.txt", "Unsubscribe from Resource Certification (RPKI) alerts"),
ROA_ALERT("email-templates/roa-alert-email.txt", "Resource Certification (RPKI) alerts for %s");
ROA_ALERT_SUBSCRIBE_CONFIRMATION_WEEKLY("email-templates/subscribe-confirmation-weekly.txt", "Your Resource Certification (RPKI) alerts subscription", true),
ROA_ALERT_SUBSCRIBE_CONFIRMATION_DAILY("email-templates/subscribe-confirmation-daily.txt", "Your Resource Certification (RPKI) alerts subscription", true),
ROA_ALERT_UNSUBSCRIBE("email-templates/unsubscribe-confirmation.txt", "Unsubscribe from Resource Certification (RPKI) alerts", false),
ROA_ALERT("email-templates/roa-alert-email.txt", "Resource Certification (RPKI) alerts for %s", true);

public final String templateName;
public final String templateSubject;
public final boolean generateUnsubcribeUrl;

private EmailTemplates(String templateName, String subject) {
EmailTemplates(String templateName, String subject, boolean generateUnsubcribeUrl) {
this.templateName = templateName;
this.templateSubject = subject;
this.generateUnsubcribeUrl = generateUnsubcribeUrl;
}


}

}
Loading

0 comments on commit 5bcbdb4

Please sign in to comment.