Skip to content

Commit 98cbed8

Browse files
feat: switch JTI validation to black-listing (#4851)
1 parent 1a700a7 commit 98cbed8

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

extensions/common/crypto/jwt-verifiable-credentials/src/main/java/org/eclipse/edc/verifiablecredentials/jwt/rules/JtiValidationRule.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package org.eclipse.edc.verifiablecredentials.jwt.rules;
1616

1717
import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames;
18+
import org.eclipse.edc.jwt.validation.jti.JtiValidationEntry;
1819
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
1920
import org.eclipse.edc.spi.iam.ClaimToken;
2021
import org.eclipse.edc.spi.monitor.Monitor;
@@ -43,13 +44,20 @@ public JtiValidationRule(JtiValidationStore jtiValidationStore, Monitor monitor)
4344
public Result<Void> checkRule(@NotNull ClaimToken toVerify, @Nullable Map<String, Object> additional) {
4445
var jti = toVerify.getStringClaim(JwtRegisteredClaimNames.JWT_ID);
4546
if (jti != null) {
46-
var entry = jtiValidationStore.findById(jti);
47+
var entry = jtiValidationStore.findById(jti); // check if existed before
48+
var res = jtiValidationStore.storeEntry(new JtiValidationEntry(jti));
49+
if (res.failed()) {
50+
return Result.failure(res.getFailureDetail());
51+
}
52+
4753
if (entry == null) {
48-
return Result.failure("The JWT id '%s' was not found".formatted(jti));
54+
return Result.success();
4955
}
5056
if (entry.isExpired()) {
51-
monitor.warning("JTI Validation entry with id " + jti + " is expired");
57+
monitor.warning("JTI Validation entry with id '%s' is expired".formatted(jti));
58+
return Result.success();
5259
}
60+
return Result.failure("The JWT id '%s' was already used.".formatted(jti));
5361
}
5462
return Result.success();
5563
}

extensions/common/crypto/jwt-verifiable-credentials/src/test/java/org/eclipse/edc/verifiablecredentials/jwt/rules/JtiValidationRuleTest.java

+32-3
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,72 @@
1717
import org.eclipse.edc.jwt.validation.jti.JtiValidationEntry;
1818
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
1919
import org.eclipse.edc.spi.iam.ClaimToken;
20+
import org.eclipse.edc.spi.result.StoreResult;
21+
import org.junit.jupiter.api.BeforeEach;
2022
import org.junit.jupiter.api.Test;
2123

2224
import java.time.Instant;
2325
import java.util.Map;
2426

2527
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
28+
import static org.mockito.ArgumentMatchers.any;
2629
import static org.mockito.ArgumentMatchers.eq;
2730
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.never;
32+
import static org.mockito.Mockito.verify;
2833
import static org.mockito.Mockito.when;
2934

3035
class JtiValidationRuleTest {
3136

3237
private final JtiValidationStore store = mock();
3338
private final JtiValidationRule rule = new JtiValidationRule(store, mock());
3439

40+
@BeforeEach
41+
void setUp() {
42+
when(store.storeEntry(any())).thenReturn(StoreResult.success());
43+
}
44+
3545
@Test
3646
void checkRule_noExpiration_success() {
3747
when(store.findById(eq("test-id"))).thenReturn(new JtiValidationEntry("test-id"));
38-
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isSucceeded();
48+
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isFailed()
49+
.detail().isEqualTo("The JWT id 'test-id' was already used.");
50+
verify(store).storeEntry(any());
3951
}
4052

4153
@Test
4254
void checkRule_withExpiration_success() {
4355
when(store.findById(eq("test-id"))).thenReturn(new JtiValidationEntry("test-id", Instant.now().plusSeconds(3600).toEpochMilli()));
44-
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isSucceeded();
56+
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isFailed()
57+
.detail().isEqualTo("The JWT id 'test-id' was already used.");
58+
verify(store).storeEntry(any());
4559
}
4660

4761
@Test
4862
void checkRule_withExpiration_alreadyExpired() {
4963
when(store.findById(eq("test-id"))).thenReturn(new JtiValidationEntry("test-id", Instant.now().minusSeconds(3600).toEpochMilli()));
5064
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isSucceeded();
65+
verify(store).storeEntry(any());
5166
}
5267

5368
@Test
5469
void checkRule_entryNotFound_success() {
5570
when(store.findById(eq("test-id"))).thenReturn(null);
71+
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isSucceeded();
72+
verify(store).storeEntry(any());
73+
}
74+
75+
@Test
76+
void checkRule_entryNotFound_storeFails_failure() {
77+
when(store.findById(eq("test-id"))).thenReturn(null);
78+
when(store.storeEntry(any())).thenReturn(StoreResult.duplicateKeys("foobar"));
5679
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().claim("jti", "test-id").build(), Map.of())).isFailed()
57-
.detail().isEqualTo("The JWT id 'test-id' was not found");
80+
.detail().isEqualTo("foobar");
81+
}
82+
83+
@Test
84+
void checkRule_whenClaimTokenNoKid() {
85+
assertThat(rule.checkRule(ClaimToken.Builder.newInstance().build(), Map.of())).isSucceeded();
86+
verify(store, never()).storeEntry(any());
5887
}
5988
}

0 commit comments

Comments
 (0)