From 818a7831dd2617958373f3986c921b34c852fc88 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:12:33 -0700 Subject: [PATCH] Add Nullability to opensaml5Main Source Set Issue gh-17823 Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com> --- ...ing-security-saml2-service-provider.gradle | 4 ++ ...nSamlAssertingPartyMetadataRepository.java | 4 +- .../saml2/internal/OpenSaml5Template.java | 33 ++++++++++--- .../OpenSaml5AuthenticationProvider.java | 46 +++++++++++-------- .../authentication/OpenSaml5Template.java | 33 ++++++++++--- .../OpenSaml5LogoutRequestValidator.java | 3 ++ .../OpenSaml5LogoutResponseValidator.java | 3 ++ .../logout/OpenSaml5Template.java | 33 ++++++++++--- .../authentication/logout/Saml2Utils.java | 3 ++ .../metadata/OpenSaml5MetadataResolver.java | 2 + .../service/metadata/OpenSaml5Template.java | 33 ++++++++++--- ...Saml5AssertingPartyMetadataRepository.java | 19 ++++---- .../registration/OpenSaml5Template.java | 33 ++++++++++--- ...OpenSaml5AuthenticationTokenConverter.java | 5 +- .../service/web/OpenSaml5Template.java | 33 ++++++++++--- ...penSaml5AuthenticationRequestResolver.java | 7 ++- .../web/authentication/OpenSaml5Template.java | 33 ++++++++++--- .../OpenSaml5LogoutRequestResolver.java | 5 +- ...outRequestValidatorParametersResolver.java | 2 + .../OpenSaml5LogoutResponseResolver.java | 12 +++-- .../logout/OpenSaml5Template.java | 33 ++++++++++--- .../TestCustomOpenSaml5Objects.java | 23 ++++------ 22 files changed, 291 insertions(+), 111 deletions(-) diff --git a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle index a34613ba30..39c3e34dc7 100644 --- a/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle +++ b/saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle @@ -138,6 +138,10 @@ javadoc { source = sourceSets.main.allJava + sourceSets.opensaml5Main.allJava } +tasks.named("compileOpensaml5MainJava") { + options.nullability.checking = "main" +} + tasks.register("opensaml5Test", Test) { useJUnitPlatform() testClassesDirs = sourceSets.opensaml5Test.output.classesDirs diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/BaseOpenSamlAssertingPartyMetadataRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/BaseOpenSamlAssertingPartyMetadataRepository.java index ca6f5644ba..5a5abbeb98 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/BaseOpenSamlAssertingPartyMetadataRepository.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/BaseOpenSamlAssertingPartyMetadataRepository.java @@ -118,7 +118,7 @@ class BaseOpenSamlAssertingPartyMetadataRepository implements AssertingPartyMeta return OpenSamlAssertingPartyDetails.withEntityDescriptor(descriptor).build(); } - private EntityDescriptor resolveSingle(EntityIdCriterion criterion) { + private @Nullable EntityDescriptor resolveSingle(EntityIdCriterion criterion) { try { return this.metadataResolver.resolveSingle(criterion); } @@ -147,7 +147,7 @@ class BaseOpenSamlAssertingPartyMetadataRepository implements AssertingPartyMeta this.metadataResolver = metadataResolver; } - abstract EntityDescriptor resolveSingle(EntityIdCriterion entityId) throws Exception; + abstract @Nullable EntityDescriptor resolveSingle(EntityIdCriterion entityId) throws Exception; abstract Iterable resolve(EntityRoleCriterion role) throws Exception; diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/internal/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/internal/OpenSaml5Template.java index 8d95015c93..7122040c92 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/internal/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/internal/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java index ba7db2718a..2fee31f67e 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java @@ -24,10 +24,13 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import javax.xml.namespace.QName; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.saml.common.assertion.AssertionValidationException; import org.opensaml.saml.common.assertion.ValidationContext; import org.opensaml.saml.common.assertion.ValidationResult; @@ -43,8 +46,11 @@ import org.opensaml.saml.saml2.assertion.impl.ProxyRestrictionConditionValidator import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Condition; import org.opensaml.saml.saml2.core.EncryptedAssertion; +import org.opensaml.saml.saml2.core.Issuer; +import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.OneTimeUse; import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Subject; import org.opensaml.saml.saml2.core.SubjectConfirmation; import org.opensaml.saml.saml2.core.SubjectConfirmationData; import org.opensaml.saml.saml2.encryption.Decrypter; @@ -52,8 +58,6 @@ import org.opensaml.xmlsec.signature.support.SignaturePrevalidator; import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; import org.springframework.core.convert.converter.Converter; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -111,6 +115,7 @@ import org.springframework.util.StringUtils; * StatusResponse * @see OpenSAML */ +@NullMarked public final class OpenSaml5AuthenticationProvider implements AuthenticationProvider { private static final String AUTHORITY = FactorGrantedAuthority.SAML_RESPONSE_AUTHORITY; @@ -367,11 +372,12 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv } catch (Exception ex) { String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), ex.getMessage()); + ((Response) Objects.requireNonNull(assertion.getParent())).getID(), ex.getMessage()); return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_ASSERTION, message)); } String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), context.getValidationFailureMessages()); + ((Response) Objects.requireNonNull(assertion.getParent())).getID(), + context.getValidationFailureMessages()); return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_ASSERTION, message)); }; } @@ -491,7 +497,6 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv public static final class InResponseToValidator implements Converter { @Override - @NonNull public Saml2ResponseValidatorResult convert(ResponseToken responseToken) { AbstractSaml2AuthenticationRequest request = responseToken.getToken().getAuthenticationRequest(); Response response = responseToken.getResponse(); @@ -510,7 +515,6 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv public static final class DestinationValidator implements Converter { @Override - @NonNull public Saml2ResponseValidatorResult convert(ResponseToken responseToken) { Response response = responseToken.getResponse(); Saml2AuthenticationToken token = responseToken.getToken(); @@ -536,15 +540,15 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv public static final class IssuerValidator implements Converter { @Override - @NonNull public Saml2ResponseValidatorResult convert(ResponseToken responseToken) { Response response = responseToken.getResponse(); Saml2AuthenticationToken token = responseToken.getToken(); - String issuer = response.getIssuer().getValue(); + Issuer issuer = response.getIssuer(); + Assert.notNull(issuer, "Response#Issuer cannot be null"); String assertingPartyEntityId = token.getRelyingPartyRegistration() .getAssertingPartyMetadata() .getEntityId(); - if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) { + if (!StringUtils.hasText(issuer.getValue()) || !assertingPartyEntityId.equals(issuer.getValue())) { String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID()); return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, message)); } @@ -642,11 +646,12 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv } catch (Exception ex) { String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), ex.getMessage()); + ((Response) Objects.requireNonNull(assertion.getParent())).getID(), ex.getMessage()); return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_ASSERTION, message)); } String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), validationContext.getValidationFailureMessages()); + ((Response) Objects.requireNonNull(assertion.getParent())).getID(), + validationContext.getValidationFailureMessages()); return Saml2ResponseValidatorResult.failure(new Saml2Error(Saml2ErrorCodes.INVALID_ASSERTION, message)); } @@ -704,7 +709,7 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv return false; } - private static String getAuthnRequestId(AbstractSaml2AuthenticationRequest serialized) { + private static @Nullable String getAuthnRequestId(@Nullable AbstractSaml2AuthenticationRequest serialized) { return (serialized != null) ? serialized.getId() : null; } @@ -835,16 +840,13 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv this.name = name; } - @NonNull @Override public QName getServicedCondition() { return this.name; } - @NonNull @Override - public ValidationResult validate(@NonNull Condition condition, @NonNull Assertion assertion, - @NonNull ValidationContext context) { + public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { return ValidationResult.VALID; } @@ -855,16 +857,15 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv private ValidSignatureAssertionValidator(@Nullable Collection newConditionValidators, @Nullable Collection newConfirmationValidators, @Nullable Collection newStatementValidators, - @Nullable org.opensaml.saml.saml2.assertion.AssertionValidator newAssertionValidator, + org.opensaml.saml.saml2.assertion.@Nullable AssertionValidator newAssertionValidator, @Nullable SignatureTrustEngine newTrustEngine, @Nullable SignaturePrevalidator newSignaturePrevalidator) { super(newConditionValidators, newConfirmationValidators, newStatementValidators, newAssertionValidator, newTrustEngine, newSignaturePrevalidator); } - @NonNull @Override - protected ValidationResult validateSignature(@NonNull Assertion token, @NonNull ValidationContext context) + protected ValidationResult validateSignature(Assertion token, ValidationContext context) throws AssertionValidationException { return ValidationResult.VALID; } @@ -895,6 +896,7 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv Response response = responseToken.response; Saml2AuthenticationToken token = responseToken.token; Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); + Assert.notNull(assertion, "samlResponse must have at least one assertion"); String username = this.principalNameConverter.convert(assertion); String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); Saml2ResponseAssertionAccessor accessor = Saml2ResponseAssertion.withResponseValue(token.getSaml2Response()) @@ -944,7 +946,11 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv throw new Saml2AuthenticationException( Saml2Error.subjectNotFound("Assertion [" + assertion.getID() + "] is missing a subject")); } - return assertion.getSubject().getNameID().getValue(); + Subject subject = assertion.getSubject(); + Assert.notNull(subject, "Assertion#Subject cannot be null"); + NameID nameId = subject.getNameID(); + Assert.notNull(nameId, "Assertion#Subject#NameID cannot be null"); + return Objects.requireNonNull(nameId.getValue()); } private static Collection grantedAuthorities(Assertion assertion) { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5Template.java index a19aef439c..c4935ee11a 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidator.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidator.java index d78014dee6..a7be2d1b06 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidator.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutRequestValidator.java @@ -16,12 +16,15 @@ package org.springframework.security.saml2.provider.service.authentication.logout; +import org.jspecify.annotations.NullMarked; + /** * An OpenSAML 5.x compatible implementation of {@link Saml2LogoutResponseValidator} * * @author Josh Cummings * @since 5.6 */ +@NullMarked public final class OpenSaml5LogoutRequestValidator implements Saml2LogoutRequestValidator { @SuppressWarnings("deprecation") diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidator.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidator.java index b59fad3fe2..ec7de8bf89 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidator.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5LogoutResponseValidator.java @@ -16,12 +16,15 @@ package org.springframework.security.saml2.provider.service.authentication.logout; +import org.jspecify.annotations.NullMarked; + /** * An OpenSAML 5.x compatible implementation of {@link Saml2LogoutResponseValidator} * * @author Josh Cummings * @since 5.6 */ +@NullMarked public final class OpenSaml5LogoutResponseValidator implements Saml2LogoutResponseValidator { @SuppressWarnings("deprecation") diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5Template.java index 05891dbfe9..39d03a2e64 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java index 75129fbeaf..69da7c35b6 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/logout/Saml2Utils.java @@ -26,6 +26,8 @@ import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterOutputStream; +import org.jspecify.annotations.NullMarked; + import org.springframework.security.saml2.Saml2Exception; /** @@ -35,6 +37,7 @@ import org.springframework.security.saml2.Saml2Exception; * * @author Josh Cummings */ +@NullMarked final class Saml2Utils { private Saml2Utils() { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolver.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolver.java index c44adabca9..5667d18a6c 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolver.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5MetadataResolver.java @@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.metadata; import java.util.function.Consumer; +import org.jspecify.annotations.NullMarked; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.springframework.security.saml2.core.OpenSamlInitializationService; @@ -31,6 +32,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP * @author Josh Cummings * @since 5.4 */ +@NullMarked public final class OpenSaml5MetadataResolver implements Saml2MetadataResolver { static { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5Template.java index 4e3cdd4277..61cc6ae63d 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/metadata/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5AssertingPartyMetadataRepository.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5AssertingPartyMetadataRepository.java index a69e7bb709..23fc92d3a0 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5AssertingPartyMetadataRepository.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5AssertingPartyMetadataRepository.java @@ -27,6 +27,9 @@ import java.util.Iterator; import java.util.function.Consumer; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.saml.criterion.EntityRoleCriterion; @@ -45,8 +48,6 @@ import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngin import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.provider.service.registration.BaseOpenSamlAssertingPartyMetadataRepository.MetadataResolverAdapter; @@ -65,6 +66,7 @@ import org.springframework.util.Assert; * @see AssertingPartyMetadataRepository * @see RelyingPartyRegistrations */ +@NullMarked public final class OpenSaml5AssertingPartyMetadataRepository implements AssertingPartyMetadataRepository { static { @@ -93,7 +95,6 @@ public final class OpenSaml5AssertingPartyMetadataRepository implements Assertin * {@inheritDoc} */ @Override - @NonNull public Iterator iterator() { return this.delegate.iterator(); } @@ -101,9 +102,8 @@ public final class OpenSaml5AssertingPartyMetadataRepository implements Assertin /** * {@inheritDoc} */ - @Nullable @Override - public AssertingPartyMetadata findByEntityId(String entityId) { + public @Nullable AssertingPartyMetadata findByEntityId(String entityId) { return this.delegate.findByEntityId(entityId); } @@ -222,7 +222,9 @@ public final class OpenSaml5AssertingPartyMetadataRepository implements Assertin } private MetadataResolver initialize(ResourceBackedMetadataResolver metadataResolver) { - metadataResolver.setParserPool(XMLObjectProviderRegistrySupport.getParserPool()); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + metadataResolver.setParserPool(pool); return BaseOpenSamlAssertingPartyMetadataRepository.initialize(metadataResolver); } @@ -264,7 +266,6 @@ public final class OpenSaml5AssertingPartyMetadataRepository implements Assertin return this.resource.getFile(); } - @NonNull @Override public InputStream getInputStream() throws IOException { return this.resource.getInputStream(); @@ -287,7 +288,7 @@ public final class OpenSaml5AssertingPartyMetadataRepository implements Assertin } @Override - public String getFilename() { + public @Nullable String getFilename() { return this.resource.getFilename(); } @@ -307,7 +308,7 @@ public final class OpenSaml5AssertingPartyMetadataRepository implements Assertin } @Override - EntityDescriptor resolveSingle(EntityIdCriterion entityId) throws Exception { + @Nullable EntityDescriptor resolveSingle(EntityIdCriterion entityId) throws Exception { return super.metadataResolver.resolveSingle(new CriteriaSet(entityId)); } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5Template.java index 3be3af2d16..76736332db 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/registration/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java index 64c626df51..6fc3a8f70c 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5AuthenticationTokenConverter.java @@ -17,6 +17,8 @@ package org.springframework.security.saml2.provider.service.web; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; @@ -35,6 +37,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 6.1 */ +@NullMarked public final class OpenSaml5AuthenticationTokenConverter implements AuthenticationConverter { private final BaseOpenSamlAuthenticationTokenConverter delegate; @@ -76,7 +79,7 @@ public final class OpenSaml5AuthenticationTokenConverter implements Authenticati * non-existent {@code registrationId} */ @Override - public Saml2AuthenticationToken convert(HttpServletRequest request) { + public @Nullable Saml2AuthenticationToken convert(HttpServletRequest request) { return this.delegate.convert(request); } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5Template.java index d19cb91842..ab3db963c3 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5AuthenticationRequestResolver.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5AuthenticationRequestResolver.java index e8c23e2d93..c8bed4b578 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5AuthenticationRequestResolver.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5AuthenticationRequestResolver.java @@ -21,6 +21,8 @@ import java.time.Instant; import java.util.function.Consumer; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.saml.saml2.core.AuthnRequest; import org.springframework.core.convert.converter.Converter; @@ -38,6 +40,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 5.7 */ +@NullMarked public final class OpenSaml5AuthenticationRequestResolver implements Saml2AuthenticationRequestResolver { private final BaseOpenSamlAuthenticationRequestResolver delegate; @@ -65,7 +68,7 @@ public final class OpenSaml5AuthenticationRequestResolver implements Saml2Authen } @Override - public T resolve(HttpServletRequest request) { + public @Nullable T resolve(HttpServletRequest request) { return this.delegate.resolve(request); } @@ -107,7 +110,7 @@ public final class OpenSaml5AuthenticationRequestResolver implements Saml2Authen * @param relayStateResolver the {@link Converter} to use * @since 5.8 */ - public void setRelayStateResolver(Converter relayStateResolver) { + public void setRelayStateResolver(Converter relayStateResolver) { Assert.notNull(relayStateResolver, "relayStateResolver cannot be null"); this.delegate.setRelayStateResolver(relayStateResolver); } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5Template.java index c832002f03..3486ec8783 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestResolver.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestResolver.java index a18c58ba87..c15ea0e7e2 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestResolver.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestResolver.java @@ -21,6 +21,8 @@ import java.time.Instant; import java.util.function.Consumer; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.saml.saml2.core.LogoutRequest; import org.springframework.core.convert.converter.Converter; @@ -39,6 +41,7 @@ import org.springframework.util.Assert; * @author Gerhard Haege * @since 5.6 */ +@NullMarked public final class OpenSaml5LogoutRequestResolver implements Saml2LogoutRequestResolver { private final BaseOpenSamlLogoutRequestResolver delegate; @@ -64,7 +67,7 @@ public final class OpenSaml5LogoutRequestResolver implements Saml2LogoutRequestR * {@inheritDoc} */ @Override - public Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication) { + public @Nullable Saml2LogoutRequest resolve(HttpServletRequest request, Authentication authentication) { return this.delegate.resolve(request, authentication); } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestValidatorParametersResolver.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestValidatorParametersResolver.java index 22556c48e3..da980ac105 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestValidatorParametersResolver.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutRequestValidatorParametersResolver.java @@ -17,6 +17,7 @@ package org.springframework.security.saml2.provider.service.web.authentication.logout; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; @@ -32,6 +33,7 @@ import org.springframework.util.Assert; * An OpenSAML-based implementation of * {@link Saml2LogoutRequestValidatorParametersResolver} */ +@NullMarked public final class OpenSaml5LogoutRequestValidatorParametersResolver implements Saml2LogoutRequestValidatorParametersResolver { diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java index 17546e74de..a6fa45885b 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5LogoutResponseResolver.java @@ -21,6 +21,7 @@ import java.time.Instant; import java.util.function.Consumer; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.opensaml.saml.saml2.core.LogoutRequest; @@ -39,6 +40,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 5.6 */ +@NullMarked public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutResponseResolver { private final BaseOpenSamlLogoutResponseResolver delegate; @@ -64,7 +66,7 @@ public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutRespons * {@inheritDoc} */ @Override - public @Nullable Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication) { + public @Nullable Saml2LogoutResponse resolve(HttpServletRequest request, @Nullable Authentication authentication) { return this.delegate.resolve(request, authentication); } @@ -72,7 +74,7 @@ public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutRespons * {@inheritDoc} */ @Override - public @Nullable Saml2LogoutResponse resolve(HttpServletRequest request, Authentication authentication, + public @Nullable Saml2LogoutResponse resolve(HttpServletRequest request, @Nullable Authentication authentication, Saml2AuthenticationException exception) { return this.delegate.resolve(request, authentication, exception); } @@ -103,12 +105,12 @@ public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutRespons private final RelyingPartyRegistration registration; - private final Authentication authentication; + private final @Nullable Authentication authentication; private final LogoutRequest logoutRequest; public LogoutResponseParameters(HttpServletRequest request, RelyingPartyRegistration registration, - Authentication authentication, LogoutRequest logoutRequest) { + @Nullable Authentication authentication, LogoutRequest logoutRequest) { this.request = request; this.registration = registration; this.authentication = authentication; @@ -128,7 +130,7 @@ public final class OpenSaml5LogoutResponseResolver implements Saml2LogoutRespons return this.registration; } - public Authentication getAuthentication() { + public @Nullable Authentication getAuthentication() { return this.authentication; } diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5Template.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5Template.java index 45a3e4be90..0b7c1019c2 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5Template.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/web/authentication/logout/OpenSaml5Template.java @@ -34,9 +34,12 @@ import java.util.Set; import javax.xml.namespace.QName; import net.shibboleth.shared.resolver.CriteriaSet; +import net.shibboleth.shared.xml.ParserPool; import net.shibboleth.shared.xml.SerializeSupport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilder; @@ -117,6 +120,7 @@ import org.springframework.web.util.UriUtils; /** * For internal use only. Subject to breaking changes at any time. */ +@NullMarked final class OpenSaml5Template implements OpenSamlOperations { private static final Log logger = LogFactory.getLog(OpenSaml5Template.class); @@ -138,7 +142,9 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public T deserialize(InputStream serialized) { try { - Document document = XMLObjectProviderRegistrySupport.getParserPool().parse(serialized); + ParserPool pool = XMLObjectProviderRegistrySupport.getParserPool(); + Assert.notNull(pool, "ParserPool must be configured"); + Document document = pool.parse(serialized); Element element = document.getDocumentElement(); UnmarshallerFactory factory = XMLObjectProviderRegistrySupport.getUnmarshallerFactory(); Unmarshaller unmarshaller = factory.getUnmarshaller(element); @@ -158,6 +164,7 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public OpenSaml5SerializationConfigurer serialize(XMLObject object) { Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(object); + Assert.notNull(marshaller, "Marshaller for " + object.getElementQName() + " must be configured"); try { return serialize(marshaller.marshall(object)); } @@ -252,7 +259,9 @@ final class OpenSaml5Template implements OpenSamlOperations { SignatureSigningParameters parameters = resolveSigningParameters(); this.components.putAll(params); Credential credential = parameters.getSigningCredential(); + Assert.notNull(credential, "credential cannot be null when signing a SAML payload"); String algorithmUri = parameters.getSignatureAlgorithm(); + Assert.notNull(algorithmUri, "algorithmUri cannot be null when signing a SAML payload"); this.components.put(Saml2ParameterNames.SIG_ALG, algorithmUri); UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry component : this.components.entrySet()) { @@ -328,14 +337,14 @@ final class OpenSaml5Template implements OpenSamlOperations { private final Collection credentials; - private String entityId; + private @Nullable String entityId; OpenSaml5VerificationConfigurer(Collection credentials) { this.credentials = credentials; } @Override - public VerificationConfigurer entityId(String entityId) { + public VerificationConfigurer entityId(@Nullable String entityId) { this.entityId = entityId; return this; } @@ -354,6 +363,7 @@ final class OpenSaml5Template implements OpenSamlOperations { } private CriteriaSet verificationCriteria(Issuer issuer) { + Assert.notNull(issuer.getValue(), "required elements must have a value"); return new CriteriaSet(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer.getValue())), new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)), new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING))); @@ -362,12 +372,21 @@ final class OpenSaml5Template implements OpenSamlOperations { @Override public Collection verify(SignableXMLObject signable) { if (signable instanceof StatusResponseType response) { + Assert.notNull(response.getID(), "Response#ID cannot be null"); + Assert.notNull(response.getIssuer(), "Response#Issuer cannot be null"); + Assert.notNull(response.getSignature(), "Response#Signature cannot be null"); return verifySignature(response.getID(), response.getIssuer(), response.getSignature()); } if (signable instanceof RequestAbstractType request) { + Assert.notNull(request.getID(), "Request#ID cannot be null"); + Assert.notNull(request.getIssuer(), "Request#Issuer cannot be null"); + Assert.notNull(request.getSignature(), "Request#Signature cannot be null"); return verifySignature(request.getID(), request.getIssuer(), request.getSignature()); } if (signable instanceof Assertion assertion) { + Assert.notNull(assertion.getID(), "Assertion#ID cannot be null"); + Assert.notNull(assertion.getIssuer(), "Assertion#Issuer cannot be null"); + Assert.notNull(assertion.getSignature(), "Assertion#Signature cannot be null"); return verifySignature(assertion.getID(), assertion.getIssuer(), assertion.getSignature()); } throw new Saml2Exception("Unsupported object of type: " + signable.getClass().getName()); @@ -408,15 +427,15 @@ final class OpenSaml5Template implements OpenSamlOperations { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature algorithm for object [" + parameters.getId() + "]")); } - if (!parameters.hasSignature()) { + byte[] signature = parameters.getSignature(); + if (signature == null) { return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Missing signature for object [" + parameters.getId() + "]")); } Collection errors = new ArrayList<>(); String algorithmUri = parameters.getAlgorithm(); try { - if (!trustEngine.validate(parameters.getSignature(), parameters.getContent(), algorithmUri, criteria, - null)) { + if (!trustEngine.validate(signature, parameters.getContent(), algorithmUri, criteria, null)) { errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, "Invalid signature for object [" + parameters.getId() + "]")); } @@ -558,7 +577,7 @@ final class OpenSaml5Template implements OpenSamlOperations { statement.getAttributes().addAll(decrypteds); } - private void decryptSubject(Subject subject) { + private void decryptSubject(@Nullable Subject subject) { if (subject != null) { if (subject.getEncryptedID() != null) { try { diff --git a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml5Objects.java b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml5Objects.java index 0ae156d86f..9360896f99 100644 --- a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml5Objects.java +++ b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/TestCustomOpenSaml5Objects.java @@ -22,6 +22,7 @@ import java.util.List; import javax.xml.namespace.QName; import net.shibboleth.shared.xml.ElementSupport; +import org.jspecify.annotations.Nullable; import org.opensaml.core.xml.AbstractXMLObject; import org.opensaml.core.xml.AbstractXMLObjectBuilder; import org.opensaml.core.xml.ElementExtensibleXMLObject; @@ -38,8 +39,6 @@ import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.AttributeValue; import org.w3c.dom.Element; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; import org.springframework.security.saml2.core.OpenSamlInitializationService; public final class TestCustomOpenSaml5Objects { @@ -103,7 +102,6 @@ public final class TestCustomOpenSaml5Objects { public static class CustomOpenSamlObjectImpl extends AbstractXMLObject implements CustomOpenSamlObject { - @NonNull private IndexedXMLObjectChildrenList unknownXMLObjects; /** @@ -113,28 +111,25 @@ public final class TestCustomOpenSaml5Objects { * represents * @param namespacePrefix the prefix for the given namespace */ - protected CustomOpenSamlObjectImpl(@Nullable String namespaceURI, @NonNull String elementLocalName, + protected CustomOpenSamlObjectImpl(@Nullable String namespaceURI, String elementLocalName, @Nullable String namespacePrefix) { super(namespaceURI, elementLocalName, namespacePrefix); super.getNamespaceManager().registerNamespaceDeclaration(new Namespace(CUSTOM_NS, TYPE_CUSTOM_PREFIX)); this.unknownXMLObjects = new IndexedXMLObjectChildrenList<>(this); } - @NonNull @Override public List getUnknownXMLObjects() { return this.unknownXMLObjects; } - @NonNull @Override - public List getUnknownXMLObjects(@NonNull QName typeOrName) { + public List getUnknownXMLObjects(QName typeOrName) { return (List) this.unknownXMLObjects.subList(typeOrName); } - @Nullable @Override - public List getOrderedChildren() { + public @Nullable List getOrderedChildren() { return Collections.unmodifiableList(this.unknownXMLObjects); } @@ -162,9 +157,8 @@ public final class TestCustomOpenSaml5Objects { public static class CustomSamlObjectBuilder extends AbstractXMLObjectBuilder { - @NonNull @Override - public CustomOpenSamlObject buildObject(@Nullable String namespaceURI, @NonNull String localName, + public CustomOpenSamlObject buildObject(@Nullable String namespaceURI, String localName, @Nullable String namespacePrefix) { return new CustomOpenSamlObjectImpl(namespaceURI, localName, namespacePrefix); } @@ -178,7 +172,7 @@ public final class TestCustomOpenSaml5Objects { } @Override - protected void marshallElementContent(@NonNull XMLObject xmlObject, @NonNull Element domElement) { + protected void marshallElementContent(XMLObject xmlObject, Element domElement) { final CustomOpenSamlObject customSamlObject = (CustomOpenSamlObject) xmlObject; for (XMLObject object : customSamlObject.getOrderedChildren()) { @@ -195,15 +189,14 @@ public final class TestCustomOpenSaml5Objects { } @Override - protected void processChildElement(@NonNull XMLObject parentXMLObject, @NonNull XMLObject childXMLObject) + protected void processChildElement(XMLObject parentXMLObject, XMLObject childXMLObject) throws UnmarshallingException { final CustomOpenSamlObject customSamlObject = (CustomOpenSamlObject) parentXMLObject; customSamlObject.getUnknownXMLObjects().add(childXMLObject); } - @NonNull @Override - protected XMLObject buildXMLObject(@NonNull Element domElement) { + protected XMLObject buildXMLObject(Element domElement) { return new CustomOpenSamlObjectImpl(SAMLConstants.SAML20_NS, AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME, CustomOpenSamlObject.TYPE_CUSTOM_PREFIX); }