[java-oidfed-common] 01/02: Improve allowed_entity_types constraint to deal with null metadatas
Codeberg
noreply at shibboleth.net
Thu Oct 8 11:27:50 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-oidfed-common.
View the commit online:
https://codeberg.org/Shibboleth/java-oidfed-common/commit/2214e41f073d61a3801b1de15f5faec17c1f95f9
commit 2214e41f073d61a3801b1de15f5faec17c1f95f9
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Oct 8 11:13:12 2026 +0300
Improve allowed_entity_types constraint to deal with null metadatas
- The previous code could not deal with all null metadata in the chain statements
- Included unit tests
---
.../impl/DefaultAllowedEntityTypesConstraint.java | 27 ++-
.../DefaultAllowedEntityTypesConstraintTest.java | 218 +++++++++++++++++++++
2 files changed, 235 insertions(+), 10 deletions(-)
diff --git a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
index ff054af..53dfb6e 100644
--- a/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
+++ b/oidfed-common-impl/src/main/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraint.java
@@ -37,6 +37,7 @@ import net.shibboleth.oidfed.metadata.payload.claim.impl.MetadataImpl;
import net.shibboleth.oidfed.metadata.payload.impl.EntityConfigurationPayloadImpl;
import net.shibboleth.oidfed.metadata.payload.impl.SubordinateStatementPayloadImpl;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.logic.ConstraintViolationException;
@@ -91,7 +92,7 @@ public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolic
@Override
protected boolean doValidate(@Nonnull final List<String> constraintData,
@Nonnull final SubordinateStatement subordinateStatement,
- @Nonnull final List<EntityStatement<?>> trustChain) {
+ @Nonnull @NonnullElements final List<EntityStatement<?>> trustChain) {
if (constraintData.contains("federation_entity")) {
log.warn("The value 'federation_entity' is not allowed for allowed_entity_types");
return false;
@@ -114,7 +115,9 @@ public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolic
log.warn("The entity type {} is not allowed in entity statement {}", entityType,
entityStatement.getSubject());
for (int j = 0; j <= i; j++) {
- final EntityStatement<?> replacement = removeMetadata(entityType, trustChain.get(j));
+ final EntityStatement<?> original = trustChain.get(j);
+ assert entityType != null; assert original != null;
+ final EntityStatement<?> replacement = removeMetadata(entityType, original);
if (replacement == null) {
log.error("Could not remove the entity type {} from metadata for {}", entityType,
entityStatement.getSubject());
@@ -138,23 +141,27 @@ public class DefaultAllowedEntityTypesConstraint extends AbstractFederationPolic
* @param entityStatement entity statement holding the metadata
* @return updated entity statement, or null if the operation failed
*/
- @Nullable private EntityStatement<?> removeMetadata(final String entityType,
- final EntityStatement<?> entityStatement) {
- final Map<String, Map<String, Object>> metadataClaims =
- new HashMap<>(entityStatement.getParsedPayload().getMetadata().getAllClaims());
+ @Nullable private EntityStatement<?> removeMetadata(@Nonnull final String entityType,
+ @Nonnull final EntityStatement<?> entityStatement) {
+ final Metadata metadata = entityStatement.getParsedPayload().getMetadata();
+ if (metadata == null) {
+ log.trace("Metadata is null, nothing to do");
+ return entityStatement;
+ }
+ final Map<String, Map<String, Object>> metadataClaims = new HashMap<>(metadata.getAllClaims());
metadataClaims.remove(entityType);
log.trace("Removed entity type {}", entityType);
- final MetadataImpl metadata = new MetadataImpl(metadataClaims);
- log.trace("Metadata contents {}", metadata);
+ final MetadataImpl resultMetadata = new MetadataImpl(metadataClaims);
+ log.trace("Metadata contents {}", resultMetadata);
if (entityStatement instanceof EntityConfiguration entityConfiguration) {
final EntityConfigurationPayloadImpl payload =
new EntityConfigurationPayloadImpl(entityConfiguration.getParsedPayload());
- payload.setMetadata(metadata);
+ payload.setMetadata(resultMetadata);
return new EntityConfigurationImpl(entityStatement.getJwt(), payload);
} else if (entityStatement instanceof SubordinateStatement subordinateStatement) {
final SubordinateStatementPayloadImpl payload =
new SubordinateStatementPayloadImpl(subordinateStatement.getParsedPayload());
- payload.setMetadata(metadata);
+ payload.setMetadata(resultMetadata);
return new SubordinateStatementImpl(entityStatement.getJwt(), payload);
}
return null;
diff --git a/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraintTest.java b/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraintTest.java
new file mode 100644
index 0000000..86bd122
--- /dev/null
+++ b/oidfed-common-impl/src/test/java/net/shibboleth/oidfed/metadata/constraints/impl/DefaultAllowedEntityTypesConstraintTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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
+ *
+ * http://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 net.shibboleth.oidfed.metadata.constraints.impl;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleAbstractTypeResolver;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.as.AuthorizationServerMetadata;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.oidfed.metadata.EntityStatement;
+import net.shibboleth.oidfed.metadata.SubordinateStatement;
+import net.shibboleth.oidfed.metadata.impl.EntityConfigurationImpl;
+import net.shibboleth.oidfed.metadata.impl.SubordinateStatementImpl;
+import net.shibboleth.oidfed.metadata.jackson.JWKSetDeserializer;
+import net.shibboleth.oidfed.metadata.payload.claim.Metadata;
+import net.shibboleth.oidfed.metadata.payload.claim.impl.MetadataImpl;
+import net.shibboleth.oidfed.testing.FederationJwtSupport;
+import net.shibboleth.oidfed.testing.TestFederationCredentials;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link DefaultAllowedEntityTypesConstraint}.
+ */
+ at SuppressWarnings("null")
+public class DefaultAllowedEntityTypesConstraintTest {
+
+ DefaultAllowedEntityTypesConstraint constraint;
+ ObjectMapper objectMapper;
+
+ String anchor = "https://anchor.example.org";
+ String intermediate = "https://intermediate.example.org";
+ String subject = "https://subject.example.org";
+
+ @BeforeClass
+ public void initMapper() {
+ objectMapper = new ObjectMapper();
+ objectMapper.registerModule(new JavaTimeModule());
+ final SimpleModule jacksonModule = new SimpleModule();
+ jacksonModule.addDeserializer(JWKSet.class, new JWKSetDeserializer());
+ final SimpleAbstractTypeResolver jacksonTypeResolver = new SimpleAbstractTypeResolver();
+ jacksonTypeResolver.addMapping(Metadata.class, MetadataImpl.class);
+ jacksonModule.setAbstractTypes(jacksonTypeResolver);
+ objectMapper.registerModule(jacksonModule);
+ }
+
+ @BeforeMethod
+ public void init() throws ComponentInitializationException {
+ constraint = new DefaultAllowedEntityTypesConstraint();
+ constraint.setObjectMapper(objectMapper);
+ constraint.setId("mockId");
+ constraint.initialize();
+ }
+
+ @Test
+ public void testNullValue_nullMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint(null, null, null));
+ }
+
+ @Test
+ public void testWrongValue_nullMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint("not a list", null, null));
+ }
+
+ @Test
+ public void testEmptyList_nullMetadata_returnsTrue() throws JsonProcessingException {
+ Assert.assertTrue(runConstraint(CollectionSupport.emptyList(), null, null));
+ }
+
+ @Test
+ public void testNullValue_emptySubordinateMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint(null, CollectionSupport.emptyMap(), null));
+ }
+
+ @Test
+ public void testWrongValue_emptySubordinateMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint("not a list", CollectionSupport.emptyMap(), null));
+ }
+
+ @Test
+ public void testEmptyList_emptySubordinateMetadata_returnsTrue() throws JsonProcessingException {
+ Assert.assertTrue(runConstraint(CollectionSupport.emptyList(), CollectionSupport.emptyMap(), null));
+ }
+
+ @Test
+ public void testNullValue_emptyConfigurationMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint(null, null, CollectionSupport.emptyMap()));
+ }
+
+ @Test
+ public void testWrongValue_emptyConfigurationMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint("not a list", null, CollectionSupport.emptyMap()));
+ }
+
+ @Test
+ public void testEmptyList_emptyConfigurationMetadata_returnsTrue() throws JsonProcessingException {
+ Assert.assertTrue(runConstraint(CollectionSupport.emptyList(), null, CollectionSupport.emptyMap()));
+ }
+
+ @Test
+ public void testNullValue_emptyMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint(null, CollectionSupport.emptyMap(), CollectionSupport.emptyMap()));
+ }
+
+ @Test
+ public void testWrongValue_emptyMetadata_returnsFalse() throws JsonProcessingException {
+ Assert.assertFalse(runConstraint("not a list", CollectionSupport.emptyMap(), CollectionSupport.emptyMap()));
+ }
+
+ @Test
+ public void testEmptyList_emptyMetadata_returnsTrue() throws JsonProcessingException {
+ Assert.assertTrue(runConstraint(CollectionSupport.emptyList(), CollectionSupport.emptyMap(),
+ CollectionSupport.emptyMap()));
+ }
+
+ protected boolean runConstraint(@Nullable final Object value,
+ @Nullable final Map<String, Map<String,Object>> subordinateMetadata,
+ @Nullable final Map<String, Map<String,Object>> leafMetadata) throws JsonProcessingException {
+ final SubordinateStatement subordinateStatement =
+ subordinateStatement(intermediate, anchor, subordinateMetadata);
+ final List<EntityStatement<?>> trustChain = new ArrayList<>(List.of(
+ entityConfiguration(subject, leafMetadata, TestFederationCredentials.signingRSKey()),
+ subordinateStatement(subject, intermediate, null),
+ subordinateStatement,
+ entityConfiguration(anchor, null, TestFederationCredentials.trustAnchorKey())));
+ return constraint.validate(value, subordinateStatement, trustChain);
+ }
+
+ @Test
+ public void testEmptyList_leafRpOpMetadata_onlyRpAllowed_opMetadataRemoved() throws JsonProcessingException {
+ final SubordinateStatement subordinateStatement =
+ subordinateStatement(intermediate, anchor, null);
+ final List<EntityStatement<?>> trustChain = new ArrayList<>(List.of(
+ entityConfiguration(subject, Map.of(
+ "openid_provider", new AuthorizationServerMetadata(Issuer.parse(subject)).toJSONObject(),
+ "openid_relying_party", new OIDCClientInformation(
+ new ClientID(subject), new OIDCClientMetadata()).toJSONObject()),
+ TestFederationCredentials.signingRSKey()),
+ subordinateStatement(subject, intermediate, null),
+ subordinateStatement,
+ entityConfiguration(anchor, null, TestFederationCredentials.trustAnchorKey())));
+ Assert.assertTrue(constraint.validate(CollectionSupport.listOf("openid_relying_party"), subordinateStatement,
+ trustChain));
+ Assert.assertNull(trustChain.get(0).getParsedPayload().getMetadata().getOpenidProviderMetadata());
+ Assert.assertNotNull(trustChain.get(0).getParsedPayload().getMetadata().getOpenidRelyingPartyMetadata());
+ }
+
+ @Nonnull
+ protected SubordinateStatement subordinateStatement(@Nonnull final String entityId, @Nonnull final String issuer,
+ @Nullable final Map<String, Map<String,Object>> metadata) throws JsonProcessingException {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(issuer)
+ .subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(TestFederationCredentials.trustAnchorKey()).toJSONObject(true));
+ if (metadata != null) {
+ builder.claim("metadata", metadata);
+ }
+ final SignedJWT jwt = FederationJwtSupport.entityStatement(
+ JWSAlgorithm.RS256, TestFederationCredentials.trustAnchorKey(), builder.build());
+ assert jwt != null;
+ return SubordinateStatementImpl.parse(jwt, objectMapper);
+ }
+
+ protected EntityConfiguration entityConfiguration(@Nonnull final String entityId,
+ @Nullable final Map<String, Map<String,Object>> metadata, @Nonnull final JWK key)
+ throws JsonProcessingException {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(key).toJSONObject(true));
+ if (metadata != null) {
+ builder.claim("metadata", metadata);
+ }
+ final SignedJWT jwt = FederationJwtSupport.entityStatement(JWSAlgorithm.RS256, key, builder.build());
+ assert jwt != null;
+ return EntityConfigurationImpl.parse(jwt, objectMapper);
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list