Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[versions]
managed-nimbus-jose-jwt = "10.9"
managed-biscuit = "4.0.1"
managed-jjwt = "0.13.0"
micronaut = "5.0.0"
micronaut-platform = "5.0.0-RC1"
Expand Down Expand Up @@ -42,6 +43,7 @@ micronaut-data = { module = "io.micronaut.data:micronaut-data-bom", version.ref
micronaut-sql = { module = "io.micronaut.sql:micronaut-sql-bom", version.ref = "micronaut-sql" }

managed-nimbus-jose-jwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "managed-nimbus-jose-jwt" }
managed-biscuit = { module = "org.biscuitsec:biscuit", version.ref = "managed-biscuit" }
managed-jjwt-api = { module = "io.jsonwebtoken:jjwt-api", version.ref = "managed-jjwt" }
managed-jjwt-impl = { module = "io.jsonwebtoken:jjwt-impl", version.ref = "managed-jjwt" }
managed-jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "managed-jjwt" }
Expand All @@ -65,4 +67,3 @@ testcontainers-mysql = { module = "org.testcontainers:testcontainers-mysql" }
testcontainers = { module = "org.testcontainers:testcontainers" }
awaitility = { module = 'org.awaitility:awaitility', version.ref = 'awaitility' }
graalvm-native-buildtools = { module = "org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin", version.ref = "graalvm-native-buildtools" }

4 changes: 4 additions & 0 deletions osv-scanner.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[IgnoredVulns]]
id = "GHSA-p53j-g8pw-4w5f"
ignoreUntil = 2026-08-22
reason = "org.biscuitsec:biscuit:4.0.1 transitively uses net.i2p.crypto:eddsa:0.3.0. BiscuitTokenValidator rejects non-canonical Ed25519 signatures before Biscuit Java verification, and the regression test covers the malleated revocation-identifier case. Remove this exception when Biscuit Java publishes a dependency path without net.i2p.crypto:eddsa or a fixed verifier is otherwise available."
30 changes: 30 additions & 0 deletions security-biscuit/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id("io.micronaut.build.internal.security-module")
}

dependencies {
api(projects.micronautSecurity)
api(libs.managed.biscuit)

compileOnly(mn.micronaut.http.server)

testAnnotationProcessor(mn.micronaut.inject.java)
testImplementation(projects.micronautSecurityJwt)
testImplementation(mnTest.micronaut.test.junit5)
testImplementation(mn.micronaut.http.client)
testImplementation(mn.micronaut.http.server.netty)
testImplementation(mnReactor.micronaut.reactor)
testImplementation(mnSerde.micronaut.serde.jackson)
testRuntimeOnly(mnLogging.logback.classic)
testRuntimeOnly(mnTest.junit.jupiter.engine)
}

tasks.withType<Test> {
useJUnitPlatform()
}

micronautBuild {
binaryCompatibility {
enabledAfter("5.1.0")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.token.biscuit;

import io.micronaut.http.HttpRequest;
import org.biscuitsec.biscuit.token.Authorizer;
import org.biscuitsec.biscuit.token.Biscuit;
import org.jspecify.annotations.Nullable;

import java.util.List;

/**
* Context available when mapping an authorized Biscuit token to an authentication.
*
* @author Sergio del Amo
* @since 5.1.0
*/
@SuppressWarnings("java:S6206") // Keep JavaBean accessors for Micronaut public API consistency.
public final class BiscuitAuthenticationContext {

private final Biscuit token;
private final Authorizer authorizer;
private final long policyIndex;
private final List<String> revocationIdentifiers;
@Nullable
private final HttpRequest<?> request;

/**
* @param token The verified Biscuit token
* @param authorizer The authorizer after successful authorization
* @param policyIndex The matched allow policy index
* @param revocationIdentifiers The token revocation identifiers
* @param request The HTTP request
*/
public BiscuitAuthenticationContext(Biscuit token,
Authorizer authorizer,
long policyIndex,
List<String> revocationIdentifiers,
@Nullable HttpRequest<?> request) {
this.token = token;
this.authorizer = authorizer;
this.policyIndex = policyIndex;
this.revocationIdentifiers = List.copyOf(revocationIdentifiers);
this.request = request;
}

/**
* @return The verified Biscuit token
*/
public Biscuit getToken() {
return token;
}

/**
* @return The authorizer after successful authorization
*/
public Authorizer getAuthorizer() {
return authorizer;
}

/**
* @return The matched allow policy index
*/
public long getPolicyIndex() {
return policyIndex;
}

/**
* @return The token revocation identifiers
*/
public List<String> getRevocationIdentifiers() {
return revocationIdentifiers;
}

/**
* @return The HTTP request
*/
@Nullable
@SuppressWarnings("java:S1452") // The request body type is intentionally unknown to authentication mappers.
public HttpRequest<?> getRequest() {
return request;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.token.biscuit;

import io.micronaut.security.authentication.Authentication;

import java.util.Optional;

/**
* Maps an authorized Biscuit token to an authentication.
*
* @author Sergio del Amo
* @since 5.1.0
*/
public interface BiscuitAuthenticationFactory {

/**
* @param context The authorized Biscuit context
* @return The authentication, if the authorized token can be mapped
*/
Optional<Authentication> createAuthentication(BiscuitAuthenticationContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.token.biscuit;

import io.micronaut.http.HttpRequest;
import org.biscuitsec.biscuit.error.Error;
import org.biscuitsec.biscuit.token.Authorizer;
import org.biscuitsec.biscuit.token.Biscuit;
import org.jspecify.annotations.Nullable;

/**
* Customizes a Biscuit authorizer before authorization runs.
*
* @author Sergio del Amo
* @since 5.1.0
*/
public interface BiscuitAuthorizerCustomizer {

/**
* Customize the authorizer for a request.
* @param authorizer The authorizer
* @param biscuit The verified Biscuit token
* @param request The HTTP request
* @throws Error If customization fails
*/
void customize(Authorizer authorizer, Biscuit biscuit, @Nullable HttpRequest<?> request) throws Error;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2017-2026 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.token.biscuit;

import io.micronaut.security.token.config.TokenConfigurationProperties;
import org.jspecify.annotations.Nullable;

import java.time.Duration;
import java.util.List;
import java.util.Set;

/**
* Configuration for Biscuit token validation.
*
* @author Sergio del Amo
* @since 5.1.0
*/
public interface BiscuitConfiguration {

/**
* Biscuit configuration prefix.
*/
String PREFIX = TokenConfigurationProperties.PREFIX + ".biscuit";

/**
* @return Whether Biscuit support is enabled.
*/
boolean isEnabled();

/**
* @return Whether the Biscuit token validator is enabled.
*/
boolean isValidatorEnabled();

/**
* @return The configured Ed25519 root public key in hexadecimal form.
*/
@Nullable
String getRootPublicKey();

/**
* @return Optional root key identifier for the configured root public key.
*/
@Nullable
Integer getRootKeyId();

/**
* @return Authorizer facts added before customizers run.
*/
List<String> getFacts();

/**
* @return Authorizer rules added before customizers run.
*/
List<String> getRules();

/**
* @return Authorizer checks added before customizers run.
*/
List<String> getChecks();

/**
* @return Authorizer policies added before customizers run.
*/
List<String> getPolicies();

/**
* @return Revocation identifiers that must be rejected.
*/
Set<String> getRevokedIdentifiers();

/**
* @return Datalog run limit configuration.
*/
RunLimitsConfiguration getRunLimits();

/**
* @return Default authentication mapping configuration.
*/
AuthenticationConfiguration getAuthentication();

/**
* Biscuit Datalog run limits.
*/
interface RunLimitsConfiguration {

/**
* @return Maximum generated facts.
*/
int getMaxFacts();

/**
* @return Maximum Datalog iterations.
*/
int getMaxIterations();

/**
* @return Maximum authorization time.
*/
Duration getMaxTime();
}

/**
* Default authentication mapping configuration.
*/
interface AuthenticationConfiguration {

/**
* @return Query used to find the authenticated principal.
*/
String getPrincipalQuery();

/**
* @return Query used to find granted roles.
*/
String getRolesQuery();
}
}
Loading
Loading