From 5e0c46ac13db98d868cb78a80a09839c2a31ef7f Mon Sep 17 00:00:00 2001 From: strehle Date: Tue, 14 Apr 2026 17:45:49 +0200 Subject: [PATCH 01/58] WIP: OpenSAML 5.1.6 upgrade This version is support BC fips. Higher versions pull directly classes from non fips package. At least a version that could work together with spring security 7.x and then boot 4.x # Conflicts: # build.gradle # dependencies.gradle # server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlDecryptionUtils.java # server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java # server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverter.java # server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpoint.java # server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaInResponseToHandlingResponseValidator.java # server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlMetadataEndpointKeyRotationTests.java # server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java --- .../saml/OpenSaml5AuthenticationProvider.java | 19 +- .../saml/OpenSaml5MetadataResolver.java | 163 ++++++++++++++++++ 2 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java index 3e438781b32..7acd50cf9cc 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java @@ -35,6 +35,7 @@ import org.opensaml.core.xml.schema.XSInteger; import org.opensaml.core.xml.schema.XSString; import org.opensaml.core.xml.schema.XSURI; +import org.opensaml.saml.common.assertion.AssertionValidationException; import org.opensaml.saml.common.assertion.ValidationContext; import org.opensaml.saml.common.assertion.ValidationResult; import org.opensaml.saml.saml2.assertion.ConditionValidator; @@ -681,7 +682,8 @@ public QName getServicedCondition() { @Nonnull @Override - public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { + public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) + throws AssertionValidationException { // applications should validate their own OneTimeUse conditions return ValidationResult.VALID; } @@ -694,7 +696,8 @@ public ValidationResult validate(Condition condition, Assertion assertion, Valid subjects, statements, null, null, null) { @Nonnull @Override - protected ValidationResult validateSignature(Assertion token, ValidationContext context) { + protected ValidationResult validateSignature(Assertion token, ValidationContext context) + throws AssertionValidationException { return ValidationResult.VALID; } }; @@ -704,24 +707,28 @@ static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine en validator) { @Nonnull @Override - protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) { + protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) + throws AssertionValidationException { return ValidationResult.VALID; } @Nonnull @Override - protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) { + protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) + throws AssertionValidationException { return ValidationResult.VALID; } @Nonnull @Override - protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) { + protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) + throws AssertionValidationException { return ValidationResult.VALID; } @Override - protected ValidationResult validateIssuer(Assertion assertion, ValidationContext context) { + protected ValidationResult validateIssuer(Assertion assertion, ValidationContext context) + throws AssertionValidationException { return ValidationResult.VALID; } }; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java new file mode 100644 index 00000000000..fcf61adcf86 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java @@ -0,0 +1,163 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import net.shibboleth.shared.xml.SerializeSupport; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Marshaller; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.KeyDescriptor; +import org.opensaml.saml.saml2.metadata.NameIDFormat; +import org.opensaml.saml.saml2.metadata.SPSSODescriptor; +import org.opensaml.saml.saml2.metadata.SingleLogoutService; +import org.opensaml.security.credential.UsageType; +import org.opensaml.xmlsec.signature.KeyInfo; +import org.opensaml.xmlsec.signature.X509Certificate; +import org.opensaml.xmlsec.signature.X509Data; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver; +import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; +import org.springframework.util.Assert; +import org.w3c.dom.Element; + +import javax.xml.namespace.QName; +import java.security.cert.CertificateEncodingException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +/** + * OpenSAML 5 compatible replacement for Spring Security's {@link OpenSamlMetadataResolver}. + *

+ * Spring Security's {@code OpenSamlMetadataResolver} uses the OpenSAML 4 internal class + * {@code net.shibboleth.utilities.java.support.xml.SerializeSupport} which is not present + * when using OpenSAML 5. This implementation uses the OpenSAML 5 equivalent + * {@code net.shibboleth.shared.xml.SerializeSupport} directly. + */ +public final class OpenSaml5MetadataResolver implements Saml2MetadataResolver { + + private Consumer entityDescriptorCustomizer = (parameters) -> { + }; + + public OpenSaml5MetadataResolver() { + } + + @Override + public String resolve(RelyingPartyRegistration relyingPartyRegistration) { + EntityDescriptor entityDescriptor = buildEntityDescriptor(relyingPartyRegistration); + return serialize(entityDescriptor); + } + + /** + * Set a {@link Consumer} for modifying the OpenSAML {@link EntityDescriptor}. + */ + public void setEntityDescriptorCustomizer(Consumer entityDescriptorCustomizer) { + Assert.notNull(entityDescriptorCustomizer, "entityDescriptorCustomizer cannot be null"); + this.entityDescriptorCustomizer = entityDescriptorCustomizer; + } + + private EntityDescriptor buildEntityDescriptor(RelyingPartyRegistration registration) { + EntityDescriptor entityDescriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME); + entityDescriptor.setEntityID(registration.getEntityId()); + SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(registration); + entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor); + this.entityDescriptorCustomizer.accept( + new OpenSamlMetadataResolver.EntityDescriptorParameters(entityDescriptor, registration)); + return entityDescriptor; + } + + private SPSSODescriptor buildSpSsoDescriptor(RelyingPartyRegistration registration) { + SPSSODescriptor spSsoDescriptor = build(SPSSODescriptor.DEFAULT_ELEMENT_NAME); + spSsoDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + spSsoDescriptor.getKeyDescriptors() + .addAll(buildKeys(registration.getSigningX509Credentials(), UsageType.SIGNING)); + spSsoDescriptor.getKeyDescriptors() + .addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION)); + spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration)); + if (registration.getSingleLogoutServiceLocation() != null) { + for (Saml2MessageBinding binding : registration.getSingleLogoutServiceBindings()) { + spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration, binding)); + } + } + if (registration.getNameIdFormat() != null) { + spSsoDescriptor.getNameIDFormats().add(buildNameIDFormat(registration)); + } + return spSsoDescriptor; + } + + private List buildKeys(Collection credentials, UsageType usageType) { + List list = new ArrayList<>(); + for (Saml2X509Credential credential : credentials) { + list.add(buildKeyDescriptor(usageType, credential.getCertificate())); + } + return list; + } + + private KeyDescriptor buildKeyDescriptor(UsageType usageType, java.security.cert.X509Certificate certificate) { + KeyDescriptor keyDescriptor = build(KeyDescriptor.DEFAULT_ELEMENT_NAME); + KeyInfo keyInfo = build(KeyInfo.DEFAULT_ELEMENT_NAME); + X509Certificate x509Certificate = build(X509Certificate.DEFAULT_ELEMENT_NAME); + X509Data x509Data = build(X509Data.DEFAULT_ELEMENT_NAME); + try { + x509Certificate.setValue(new String(Base64.getEncoder().encode(certificate.getEncoded()))); + } catch (CertificateEncodingException ex) { + throw new Saml2Exception("Cannot encode certificate " + certificate); + } + x509Data.getX509Certificates().add(x509Certificate); + keyInfo.getX509Datas().add(x509Data); + keyDescriptor.setUse(usageType); + keyDescriptor.setKeyInfo(keyInfo); + return keyDescriptor; + } + + private AssertionConsumerService buildAssertionConsumerService(RelyingPartyRegistration registration) { + AssertionConsumerService acs = build(AssertionConsumerService.DEFAULT_ELEMENT_NAME); + acs.setLocation(registration.getAssertionConsumerServiceLocation()); + acs.setBinding(registration.getAssertionConsumerServiceBinding().getUrn()); + acs.setIndex(1); + return acs; + } + + private SingleLogoutService buildSingleLogoutService(RelyingPartyRegistration registration, + Saml2MessageBinding binding) { + SingleLogoutService slo = build(SingleLogoutService.DEFAULT_ELEMENT_NAME); + slo.setLocation(registration.getSingleLogoutServiceLocation()); + slo.setResponseLocation(registration.getSingleLogoutServiceResponseLocation()); + slo.setBinding(binding.getUrn()); + return slo; + } + + private NameIDFormat buildNameIDFormat(RelyingPartyRegistration registration) { + NameIDFormat nameIdFormat = build(NameIDFormat.DEFAULT_ELEMENT_NAME); + nameIdFormat.setURI(registration.getNameIdFormat()); + return nameIdFormat; + } + + private String serialize(XMLObject xmlObject) { + try { + Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(xmlObject); + if (marshaller == null) { + throw new Saml2Exception("No marshaller found for " + xmlObject.getClass().getName()); + } + Element element = marshaller.marshall(xmlObject); + return SerializeSupport.prettyPrintXML(element); + } catch (MarshallingException ex) { + throw new Saml2Exception("Failed to serialize metadata", ex); + } + } + + @SuppressWarnings("unchecked") + private T build(QName elementName) { + return (T) XMLObjectProviderRegistrySupport.getBuilderFactory() + .getBuilder(elementName) + .buildObject(elementName); + } +} + From 99ec576cedf0146f7dfb8c1b7665206f42381e66 Mon Sep 17 00:00:00 2001 From: strehle Date: Fri, 17 Apr 2026 19:57:05 +0200 Subject: [PATCH 02/58] rebase back to spring security --- .../saml/OpenSaml5MetadataResolver.java | 163 ------------------ 1 file changed, 163 deletions(-) delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java deleted file mode 100644 index fcf61adcf86..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5MetadataResolver.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import net.shibboleth.shared.xml.SerializeSupport; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.core.xml.io.MarshallingException; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.saml2.metadata.AssertionConsumerService; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml.saml2.metadata.KeyDescriptor; -import org.opensaml.saml.saml2.metadata.NameIDFormat; -import org.opensaml.saml.saml2.metadata.SPSSODescriptor; -import org.opensaml.saml.saml2.metadata.SingleLogoutService; -import org.opensaml.security.credential.UsageType; -import org.opensaml.xmlsec.signature.KeyInfo; -import org.opensaml.xmlsec.signature.X509Certificate; -import org.opensaml.xmlsec.signature.X509Data; -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver; -import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.util.Assert; -import org.w3c.dom.Element; - -import javax.xml.namespace.QName; -import java.security.cert.CertificateEncodingException; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; -import java.util.List; -import java.util.function.Consumer; - -/** - * OpenSAML 5 compatible replacement for Spring Security's {@link OpenSamlMetadataResolver}. - *

- * Spring Security's {@code OpenSamlMetadataResolver} uses the OpenSAML 4 internal class - * {@code net.shibboleth.utilities.java.support.xml.SerializeSupport} which is not present - * when using OpenSAML 5. This implementation uses the OpenSAML 5 equivalent - * {@code net.shibboleth.shared.xml.SerializeSupport} directly. - */ -public final class OpenSaml5MetadataResolver implements Saml2MetadataResolver { - - private Consumer entityDescriptorCustomizer = (parameters) -> { - }; - - public OpenSaml5MetadataResolver() { - } - - @Override - public String resolve(RelyingPartyRegistration relyingPartyRegistration) { - EntityDescriptor entityDescriptor = buildEntityDescriptor(relyingPartyRegistration); - return serialize(entityDescriptor); - } - - /** - * Set a {@link Consumer} for modifying the OpenSAML {@link EntityDescriptor}. - */ - public void setEntityDescriptorCustomizer(Consumer entityDescriptorCustomizer) { - Assert.notNull(entityDescriptorCustomizer, "entityDescriptorCustomizer cannot be null"); - this.entityDescriptorCustomizer = entityDescriptorCustomizer; - } - - private EntityDescriptor buildEntityDescriptor(RelyingPartyRegistration registration) { - EntityDescriptor entityDescriptor = build(EntityDescriptor.DEFAULT_ELEMENT_NAME); - entityDescriptor.setEntityID(registration.getEntityId()); - SPSSODescriptor spSsoDescriptor = buildSpSsoDescriptor(registration); - entityDescriptor.getRoleDescriptors(SPSSODescriptor.DEFAULT_ELEMENT_NAME).add(spSsoDescriptor); - this.entityDescriptorCustomizer.accept( - new OpenSamlMetadataResolver.EntityDescriptorParameters(entityDescriptor, registration)); - return entityDescriptor; - } - - private SPSSODescriptor buildSpSsoDescriptor(RelyingPartyRegistration registration) { - SPSSODescriptor spSsoDescriptor = build(SPSSODescriptor.DEFAULT_ELEMENT_NAME); - spSsoDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); - spSsoDescriptor.getKeyDescriptors() - .addAll(buildKeys(registration.getSigningX509Credentials(), UsageType.SIGNING)); - spSsoDescriptor.getKeyDescriptors() - .addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION)); - spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration)); - if (registration.getSingleLogoutServiceLocation() != null) { - for (Saml2MessageBinding binding : registration.getSingleLogoutServiceBindings()) { - spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration, binding)); - } - } - if (registration.getNameIdFormat() != null) { - spSsoDescriptor.getNameIDFormats().add(buildNameIDFormat(registration)); - } - return spSsoDescriptor; - } - - private List buildKeys(Collection credentials, UsageType usageType) { - List list = new ArrayList<>(); - for (Saml2X509Credential credential : credentials) { - list.add(buildKeyDescriptor(usageType, credential.getCertificate())); - } - return list; - } - - private KeyDescriptor buildKeyDescriptor(UsageType usageType, java.security.cert.X509Certificate certificate) { - KeyDescriptor keyDescriptor = build(KeyDescriptor.DEFAULT_ELEMENT_NAME); - KeyInfo keyInfo = build(KeyInfo.DEFAULT_ELEMENT_NAME); - X509Certificate x509Certificate = build(X509Certificate.DEFAULT_ELEMENT_NAME); - X509Data x509Data = build(X509Data.DEFAULT_ELEMENT_NAME); - try { - x509Certificate.setValue(new String(Base64.getEncoder().encode(certificate.getEncoded()))); - } catch (CertificateEncodingException ex) { - throw new Saml2Exception("Cannot encode certificate " + certificate); - } - x509Data.getX509Certificates().add(x509Certificate); - keyInfo.getX509Datas().add(x509Data); - keyDescriptor.setUse(usageType); - keyDescriptor.setKeyInfo(keyInfo); - return keyDescriptor; - } - - private AssertionConsumerService buildAssertionConsumerService(RelyingPartyRegistration registration) { - AssertionConsumerService acs = build(AssertionConsumerService.DEFAULT_ELEMENT_NAME); - acs.setLocation(registration.getAssertionConsumerServiceLocation()); - acs.setBinding(registration.getAssertionConsumerServiceBinding().getUrn()); - acs.setIndex(1); - return acs; - } - - private SingleLogoutService buildSingleLogoutService(RelyingPartyRegistration registration, - Saml2MessageBinding binding) { - SingleLogoutService slo = build(SingleLogoutService.DEFAULT_ELEMENT_NAME); - slo.setLocation(registration.getSingleLogoutServiceLocation()); - slo.setResponseLocation(registration.getSingleLogoutServiceResponseLocation()); - slo.setBinding(binding.getUrn()); - return slo; - } - - private NameIDFormat buildNameIDFormat(RelyingPartyRegistration registration) { - NameIDFormat nameIdFormat = build(NameIDFormat.DEFAULT_ELEMENT_NAME); - nameIdFormat.setURI(registration.getNameIdFormat()); - return nameIdFormat; - } - - private String serialize(XMLObject xmlObject) { - try { - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(xmlObject); - if (marshaller == null) { - throw new Saml2Exception("No marshaller found for " + xmlObject.getClass().getName()); - } - Element element = marshaller.marshall(xmlObject); - return SerializeSupport.prettyPrintXML(element); - } catch (MarshallingException ex) { - throw new Saml2Exception("Failed to serialize metadata", ex); - } - } - - @SuppressWarnings("unchecked") - private T build(QName elementName) { - return (T) XMLObjectProviderRegistrySupport.getBuilderFactory() - .getBuilder(elementName) - .buildObject(elementName); - } -} - From 074b1dfba24bade449216bf25b2d66a9308b50d5 Mon Sep 17 00:00:00 2001 From: strehle Date: Sat, 18 Apr 2026 12:12:09 +0200 Subject: [PATCH 03/58] cleanup # Conflicts: # server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestCustomOpenSamlObjects.java --- .../saml/OpenSaml5AuthenticationProvider.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java index 7acd50cf9cc..3e438781b32 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java @@ -35,7 +35,6 @@ import org.opensaml.core.xml.schema.XSInteger; import org.opensaml.core.xml.schema.XSString; import org.opensaml.core.xml.schema.XSURI; -import org.opensaml.saml.common.assertion.AssertionValidationException; import org.opensaml.saml.common.assertion.ValidationContext; import org.opensaml.saml.common.assertion.ValidationResult; import org.opensaml.saml.saml2.assertion.ConditionValidator; @@ -682,8 +681,7 @@ public QName getServicedCondition() { @Nonnull @Override - public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) - throws AssertionValidationException { + public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { // applications should validate their own OneTimeUse conditions return ValidationResult.VALID; } @@ -696,8 +694,7 @@ public ValidationResult validate(Condition condition, Assertion assertion, Valid subjects, statements, null, null, null) { @Nonnull @Override - protected ValidationResult validateSignature(Assertion token, ValidationContext context) - throws AssertionValidationException { + protected ValidationResult validateSignature(Assertion token, ValidationContext context) { return ValidationResult.VALID; } }; @@ -707,28 +704,24 @@ static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine en validator) { @Nonnull @Override - protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) - throws AssertionValidationException { + protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) { return ValidationResult.VALID; } @Nonnull @Override - protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) - throws AssertionValidationException { + protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) { return ValidationResult.VALID; } @Nonnull @Override - protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) - throws AssertionValidationException { + protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) { return ValidationResult.VALID; } @Override - protected ValidationResult validateIssuer(Assertion assertion, ValidationContext context) - throws AssertionValidationException { + protected ValidationResult validateIssuer(Assertion assertion, ValidationContext context) { return ValidationResult.VALID; } }; From b8adba46ce69ad657472f4f4db8e3bb702ced44c Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 00:51:13 +0300 Subject: [PATCH 04/58] Add spring-boot-sql dependency for Spring Boot 4 Spring Boot 4 moved DependsOnDatabaseInitialization to a separate spring-boot-sql module. Added this dependency to the server module where it's needed. Related to Spring Boot 4 migration. --- gradle/libs.versions.toml | 3 ++- server/build.gradle | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c87bb9fa917..1962648d930 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ orgJson = "20251224" passay = "2.0.0" selenium = "4.44.0" sonarqube = "7.3.0.8198" -springBoot = "3.5.14" +springBoot = "4.0.0" springDependencyManagement = "1.1.7" springDocOpenapi = "2.8.17" statsdClient = "3.1.0" @@ -161,6 +161,7 @@ springWebMvc = { module = "org.springframework:spring-webmvc" } # Spring Boot springBootBom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springBoot" } +springBootSql = { module = "org.springframework.boot:spring-boot-sql" } springBootStarter = { module = "org.springframework.boot:spring-boot-starter" } springBootStarterLog4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2" } springBootStarterMail = { module = "org.springframework.boot:spring-boot-starter-mail" } diff --git a/server/build.gradle b/server/build.gradle index 8551e765ed4..1fb61505032 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -23,6 +23,7 @@ dependencies { implementation(libs.springSecurityWeb) implementation(libs.springSecurityConfig) implementation(libs.springBootStarterMail) + implementation(libs.springBootSql) implementation(libs.openSamlApi) implementation(libs.springSecuritySamlServiceProvider) implementation(libs.xmlSecurity) From 1935a7e8c082f41533aa40f986c48f8fab849520 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 00:54:51 +0300 Subject: [PATCH 05/58] Add spring-security-access dependency for Spring Security 7 Spring Security 7 moved vote classes (AuthenticatedVoter, RoleVoter, UnanimousBased) to a separate spring-security-access module that is not automatically included. Added explicit dependency. Related to Spring Boot 4 migration. --- gradle/libs.versions.toml | 1 + server/build.gradle | 1 + 2 files changed, 2 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1962648d930..d7144ff1cfe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -179,6 +179,7 @@ springDocOpenapi = { module = "org.springdoc:springdoc-openapi-starter-webmvc-ui springRestdocs = { module = "org.springframework.restdocs:spring-restdocs-mockmvc" } # Spring Security +springSecurityAccess = { module = "org.springframework.security:spring-security-access" } springSecurityConfig = { module = "org.springframework.security:spring-security-config" } springSecurityCore = { module = "org.springframework.security:spring-security-core" } springSecurityLdap = { module = "org.springframework.security:spring-security-ldap" } diff --git a/server/build.gradle b/server/build.gradle index 1fb61505032..5b5d15485eb 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation(libs.springJdbc) implementation(libs.springWeb) implementation(libs.springSecurityCore) + implementation(libs.springSecurityAccess) implementation(libs.springSecurityWeb) implementation(libs.springSecurityConfig) implementation(libs.springBootStarterMail) From 3a60a0a471e2ef4c5167d3f91ce589e76fabc574 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 01:01:45 +0300 Subject: [PATCH 06/58] Update autoconfigure package imports for Spring Boot 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spring Boot 4 moved autoconfigure classes to technology-specific modules: - org.springframework.boot.autoconfigure.jdbc → org.springframework.boot.jdbc.autoconfigure - org.springframework.boot.autoconfigure.transaction → org.springframework.boot.transaction.autoconfigure Updated DatabaseConfiguration imports. Related to Spring Boot 4 migration. --- .../identity/uaa/db/beans/DatabaseConfiguration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/db/beans/DatabaseConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/beans/DatabaseConfiguration.java index 9063e7e4fcc..9e5d0ed252c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/db/beans/DatabaseConfiguration.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/db/beans/DatabaseConfiguration.java @@ -6,9 +6,9 @@ import org.cloudfoundry.identity.uaa.resources.jdbc.MySqlLimitSqlAdapter; import org.cloudfoundry.identity.uaa.resources.jdbc.PostgresLimitSqlAdapter; import org.jspecify.annotations.NonNull; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; +import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; From 3e453b90a18fd678f94e5691ef34deef9531ffa0 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 01:02:28 +0300 Subject: [PATCH 07/58] Remove WebMvcAutoConfiguration import and update test autoconfigure Spring Boot 4 reorganized autoconfigure packages. - Removed unused WebMvcAutoConfiguration import from WebConfig - Updated test annotation to use new autoconfigure package locations Related to Spring Boot 4 migration. # Conflicts: # server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java --- server/build.gradle | 2 ++ .../cloudfoundry/identity/uaa/web/WebConfig.java | 14 ++++++++++++-- .../uaa/annotations/WithDatabaseContext.java | 6 +++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/server/build.gradle b/server/build.gradle index 5b5d15485eb..02e0ac422f8 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -25,6 +25,8 @@ dependencies { implementation(libs.springSecurityConfig) implementation(libs.springBootStarterMail) implementation(libs.springBootSql) + implementation(libs.springBootJdbc) + implementation(libs.springBootTransaction) implementation(libs.openSamlApi) implementation(libs.springSecuritySamlServiceProvider) implementation(libs.xmlSecurity) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java index 83fad996dba..33d91c067a9 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java @@ -1,17 +1,19 @@ package org.cloudfoundry.identity.uaa.web; +import jakarta.annotation.PostConstruct; import org.cloudfoundry.identity.uaa.authentication.manager.AutologinRequestConverter; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import java.util.List; /** - * Web stack configuration. It relies on Spring Boot's {@link WebMvcAutoConfiguration}, + * Web stack configuration. It relies on Spring Boot's Web MVC auto-configuration, * with a few adjustments in a properties file to match the legacy behavior from UAA. */ @Configuration @@ -23,4 +25,12 @@ class WebConfig implements WebMvcConfigurer { public void extendMessageConverters(List> converters) { converters.add(new AutologinRequestConverter()); } + + @Autowired + private RequestMappingHandlerAdapter requestMappingHandlerAdapter; + + @PostConstruct + public void init() { + requestMappingHandlerAdapter.setIgnoreDefaultModelOnRedirect(false); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/annotations/WithDatabaseContext.java b/server/src/test/java/org/cloudfoundry/identity/uaa/annotations/WithDatabaseContext.java index f0cc7c1901a..b76cfc228aa 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/annotations/WithDatabaseContext.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/annotations/WithDatabaseContext.java @@ -7,9 +7,9 @@ import org.cloudfoundry.identity.uaa.util.beans.PasswordEncoderConfig; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; +import org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.web.WebAppConfiguration; From 4042208b8df1e4c5c85ca0af73dbfa42d5d83c48 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 12:10:49 +0300 Subject: [PATCH 08/58] Configure HttpClient connection timeout via ConnectionConfig Replace deprecated HttpComponentsClientHttpRequestFactory.setConnectTimeout() with ConnectionConfig.setConnectTimeout() on PoolingHttpClientConnectionManager. The connection timeout is now configured at the connection manager level using the recommended ConnectionConfig.Builder API. --- .../identity/uaa/util/UaaHttpRequestUtils.java | 18 ++++++++++++------ .../uaa/util/UaaHttpRequestUtilsTest.java | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java index 00747ac3461..6d4639146a3 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java @@ -17,6 +17,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; import org.apache.hc.client5.http.HttpRequestRetryStrategy; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.DefaultRedirectStrategy; import org.apache.hc.client5.http.impl.NoopUserTokenHandler; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; @@ -38,6 +39,7 @@ import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.util.TextUtils; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import org.cloudfoundry.identity.uaa.impl.config.RestTemplateConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,28 +69,26 @@ public abstract class UaaHttpRequestUtils { private static final Logger logger = LoggerFactory.getLogger(UaaHttpRequestUtils.class); public static ClientHttpRequestFactory createRequestFactory(boolean skipSslValidation, int timeout) { - return createRequestFactory(getClientBuilder(skipSslValidation, 10, 5, 0, 2000, 0), timeout, timeout); + return createRequestFactory(getClientBuilder(skipSslValidation, 10, 5, 0, 2000, 0, timeout), timeout, timeout); } public static ClientHttpRequestFactory createRequestFactory(boolean skipSslValidation, int timeout, int readTimeout, int poolSize, int defaultMaxPerRoute, int maxKeepAlive, int validateAfterInactivity, int retryCount) { - return createRequestFactory(getClientBuilder(skipSslValidation, poolSize, defaultMaxPerRoute, maxKeepAlive, validateAfterInactivity, retryCount), timeout, readTimeout); + return createRequestFactory(getClientBuilder(skipSslValidation, poolSize, defaultMaxPerRoute, maxKeepAlive, validateAfterInactivity, retryCount, timeout), timeout, readTimeout); } public static ClientHttpRequestFactory createRequestFactory(boolean skipSslValidation, int timeout, int readTimeout, RestTemplateConfig restTemplateConfig) { - return createRequestFactory(getClientBuilder(skipSslValidation, restTemplateConfig.maxTotal, restTemplateConfig.maxPerRoute, restTemplateConfig.maxKeepAlive, restTemplateConfig.validateAfterInactivity, restTemplateConfig.retryCount), timeout, readTimeout); + return createRequestFactory(getClientBuilder(skipSslValidation, restTemplateConfig.maxTotal, restTemplateConfig.maxPerRoute, restTemplateConfig.maxKeepAlive, restTemplateConfig.validateAfterInactivity, restTemplateConfig.retryCount, timeout), timeout, readTimeout); } protected static ClientHttpRequestFactory createRequestFactory(HttpClientBuilder builder, int timeoutInMs, int readTimeoutInMs) { HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(builder.build()); - // Manual migration to `SocketConfig.Builder.setSoTimeout(Timeout)` necessary; see: https://docs.spring.io/spring-framework/docs/6.0.0/javadoc-api/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.html#setReadTimeout(int) httpComponentsClientHttpRequestFactory.setReadTimeout(readTimeoutInMs); httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(timeoutInMs); - httpComponentsClientHttpRequestFactory.setConnectTimeout(timeoutInMs); return httpComponentsClientHttpRequestFactory; } - protected static HttpClientBuilder getClientBuilder(boolean skipSslValidation, int poolSize, int defaultMaxPerRoute, int maxKeepAlive, int validateAfterInactivity, int retryCount) { + protected static HttpClientBuilder getClientBuilder(boolean skipSslValidation, int poolSize, int defaultMaxPerRoute, int maxKeepAlive, int validateAfterInactivity, int retryCount, int connectTimeoutInMs) { HttpClientBuilder builder = HttpClients.custom() .useSystemProperties() .setUserTokenHandler(NoopUserTokenHandler.INSTANCE) @@ -111,6 +111,12 @@ protected static HttpClientBuilder getClientBuilder(boolean skipSslValidation, i cm.setMaxTotal(poolSize); cm.setDefaultMaxPerRoute(defaultMaxPerRoute); cm.setValidateAfterInactivity(TimeValue.of(validateAfterInactivity, TimeUnit.MILLISECONDS)); + + ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(connectTimeoutInMs)) + .build(); + cm.setDefaultConnectionConfig(connectionConfig); + builder.setConnectionManager(cm); if (maxKeepAlive <= 0) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtilsTest.java index e48a614b59c..ca96c9a3e71 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtilsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtilsTest.java @@ -128,7 +128,7 @@ void httpsIpProxy() { } public void testHttpProxy(String url, int expectedPort, String expectedHost, boolean wantHandlerInvoked) { - HttpClientBuilder builder = UaaHttpRequestUtils.getClientBuilder(true, 20, 2, 5, 2000, 2); + HttpClientBuilder builder = UaaHttpRequestUtils.getClientBuilder(true, 20, 2, 5, 2000, 2, 3000); HttpRoutePlanner planner = (HttpRoutePlanner) ReflectionTestUtils.getField(builder.build(), "routePlanner"); SystemProxyRoutePlanner routePlanner = new SystemProxyRoutePlanner(planner); builder.setRoutePlanner(routePlanner); From 45b0dfd8c1340b4ba8fde53e979528c8e878d4cd Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 12:15:34 +0300 Subject: [PATCH 09/58] Migrate HttpHeaders.containsKey to containsHeader for Spring 7 Spring Framework 7 replaced HttpHeaders.containsKey() with containsHeader(). This updates all occurrences in OAuth2 token handling code and test utilities. Related to Spring Boot 4 migration. --- .../AuthorizationCodeAccessTokenProvider.java | 2 +- .../uaa/oauth/token/OAuth2AccessTokenSupport.java | 2 +- .../error/OAuth2AuthenticationEntryPoint.java | 2 +- .../identity/uaa/ServerRunningExtension.java | 2 +- .../ExternalOAuthAuthenticationManagerTest.java | 14 +++++++------- .../uaa/test/network/NetworkTestUtils.java | 8 ++++---- .../statsd/integration/IntegrationTestUtils.java | 2 +- .../ScimGroupEndpointsIntegrationTests.java | 2 +- .../uaa/integration/util/IntegrationTestUtils.java | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/grant/AuthorizationCodeAccessTokenProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/grant/AuthorizationCodeAccessTokenProvider.java index f257bd17f0f..1f3954f0913 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/grant/AuthorizationCodeAccessTokenProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/grant/AuthorizationCodeAccessTokenProvider.java @@ -80,7 +80,7 @@ public String obtainAuthorizationCode(OAuth2ProtectedResourceDetails details, Ac ResponseExtractor> extractor = new ResponseExtractor<>() { @Override public ResponseEntity extractData(ClientHttpResponse response) throws IOException { - if (response.getHeaders().containsKey("Set-Cookie")) { + if (response.getHeaders().containsHeader("Set-Cookie")) { copy.setCookie(response.getHeaders().getFirst("Set-Cookie")); } return delegate.extractData(response); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupport.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupport.java index 063218c1dbe..02581a46bc8 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupport.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupport.java @@ -133,7 +133,7 @@ protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2Prot ResponseExtractor extractor = new ResponseExtractor<>() { @Override public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException { - if (response.getHeaders().containsKey("Set-Cookie")) { + if (response.getHeaders().containsHeader("Set-Cookie")) { copy.setCookie(response.getHeaders().getFirst("Set-Cookie")); } return delegate.extractData(response); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/OAuth2AuthenticationEntryPoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/OAuth2AuthenticationEntryPoint.java index 1e09dc8c091..d47bbaa976f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/OAuth2AuthenticationEntryPoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/OAuth2AuthenticationEntryPoint.java @@ -45,7 +45,7 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A protected ResponseEntity enhanceResponse(ResponseEntity response, Exception exception) { HttpHeaders headers = response.getHeaders(); String existing = null; - if (headers.containsKey(WWW_AUTHENTICATE)) { + if (headers.containsHeader(WWW_AUTHENTICATE)) { existing = extractTypePrefix(headers.getFirst(WWW_AUTHENTICATE)); } StringBuilder builder = new StringBuilder(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java b/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java index 2f994d504b1..2bc13e14163 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java @@ -271,7 +271,7 @@ public ResponseEntity postForRedirect(String path, HttpHeaders headers, Mu } headers.remove("Cookie"); - if (exchange.getHeaders().containsKey("Set-Cookie")) { + if (exchange.getHeaders().containsHeader("Set-Cookie")) { for (String cookie : exchange.getHeaders().get("Set-Cookie")) { headers.add("Cookie", cookie); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java index f31894bd580..3aba7797f69 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java @@ -952,10 +952,10 @@ void oidcJwtBearerProviderProxyThrowException() throws JOSEException, MalformedU })); HttpEntity httpEntity = httpEntityArgumentCaptor.getValue(); HttpHeaders httpHeaders = httpEntity.getHeaders(); - assertThat(httpHeaders).containsKey("Authorization") - .containsEntry("Authorization", Collections.singletonList("Basic aWRlbnRpdHk6c2VjcmV0")) - .containsKey("Accept") - .containsEntry("Content-Type", Collections.singletonList("application/x-www-form-urlencoded")); + assertThat(httpHeaders.containsHeader("Authorization")).isTrue(); + assertThat(httpHeaders.get("Authorization")).isEqualTo(Collections.singletonList("Basic aWRlbnRpdHk6c2VjcmV0")); + assertThat(httpHeaders.containsHeader("Accept")).isTrue(); + assertThat(httpHeaders.get("Content-Type")).isEqualTo(Collections.singletonList("application/x-www-form-urlencoded")); } @Test @@ -1012,7 +1012,7 @@ void oidcPasswordGrantWithForwardHeader() throws JOSEException, MalformedURLExce assertThat(headers.getAccept()).isEqualTo(Collections.singletonList(MediaType.APPLICATION_JSON)); assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED); assertAuthorizationHeaderIsSetAndStartsWithBasic(headers); - assertThat(headers).containsKey("X-Forwarded-For"); + assertThat(headers.containsHeader("X-Forwarded-For")).isTrue(); final List xForwardedForHeaders = headers.get("X-Forwarded-For"); assertThat(xForwardedForHeaders).hasSize(1); assertThat(xForwardedForHeaders.getFirst()).isEqualTo("203.0.113.1"); @@ -1117,7 +1117,7 @@ void oidcPasswordGrantWithPrompts() throws MalformedURLException, JOSEException assertThat(headers.getAccept()).isEqualTo(Collections.singletonList(MediaType.APPLICATION_JSON)); assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED); assertAuthorizationHeaderIsSetAndStartsWithBasic(headers); - assertThat(headers).doesNotContainKey("X-Forwarded-For"); + assertThat(headers.containsHeader("X-Forwarded-For")).isFalse(); } @Test @@ -1165,7 +1165,7 @@ public RestTemplate getRestTemplate(AbstractExternalOAuthIdentityProviderDefinit } private static void assertAuthorizationHeaderIsSetAndStartsWithBasic(final HttpHeaders headers) { - assertThat(headers).containsKey("Authorization"); + assertThat(headers.containsHeader("Authorization")).isTrue(); final List authorizationHeaders = headers.get("Authorization"); assertThat(authorizationHeaders).hasSize(1); assertThat(authorizationHeaders.getFirst()).startsWith("Basic "); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/test/network/NetworkTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/network/NetworkTestUtils.java index 396d196a190..9c9af7f819e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/test/network/NetworkTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/network/NetworkTestUtils.java @@ -167,11 +167,11 @@ public boolean wasInvoked() { public void handle(HttpExchange httpExchange) throws IOException { wasInvoked = true; HttpsExchange exchange = (HttpsExchange) httpExchange; - for (Map.Entry> entry : headers.entrySet()) { - for (String value : entry.getValue()) { - exchange.getResponseHeaders().add(entry.getKey(), value); + headers.forEach((key, values) -> { + for (String value : values) { + exchange.getResponseHeaders().add(key, value); } - } + }); exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); exchange.sendResponseHeaders(200, responseBody.length()); OutputStream os = exchange.getResponseBody(); diff --git a/statsd-lib/src/test/java/org/cloudfoundry/identity/statsd/integration/IntegrationTestUtils.java b/statsd-lib/src/test/java/org/cloudfoundry/identity/statsd/integration/IntegrationTestUtils.java index 8e7a1a6b1d9..0bacdb7fc10 100644 --- a/statsd-lib/src/test/java/org/cloudfoundry/identity/statsd/integration/IntegrationTestUtils.java +++ b/statsd-lib/src/test/java/org/cloudfoundry/identity/statsd/integration/IntegrationTestUtils.java @@ -15,7 +15,7 @@ public class IntegrationTestUtils { static final String TEST_PASSWORD = "koala"; static void copyCookies(ResponseEntity response, HttpHeaders headers) { - if (response.getHeaders().containsKey("Set-Cookie")) { + if (response.getHeaders().containsHeader("Set-Cookie")) { for (String cookie : response.getHeaders().get("Set-Cookie")) { headers.add("Cookie", cookie); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java index 52ab4006cc1..6bcdbbd88c0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java @@ -619,7 +619,7 @@ private OAuth2AccessToken getAccessToken(String clientId, String clientSecret, S .contains("username") .contains("password"); - if (response.getHeaders().containsKey("Set-Cookie")) { + if (response.getHeaders().containsHeader("Set-Cookie")) { String cookie = response.getHeaders().getFirst("Set-Cookie"); int nameLength = cookie.indexOf('='); cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index 628d79ed7eb..db7657fa882 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -1548,7 +1548,7 @@ public static Map getAuthorizationCodeTokenMap(ServerRunningExte } public static void extractCookies(ResponseEntity response, BasicCookieStore cookies) { - if (response.getHeaders().containsKey("Set-Cookie")) { + if (response.getHeaders().containsHeader("Set-Cookie")) { for (String cookie : response.getHeaders().get("Set-Cookie")) { int nameLength = cookie.indexOf('='); cookies.addCookie(new BasicClientCookie(cookie.substring(0, nameLength), cookie.substring(nameLength + 1))); @@ -1557,7 +1557,7 @@ public static void extractCookies(ResponseEntity response, BasicCookieStore c } public static void copyCookies(ResponseEntity response, HttpHeaders headers) { - if (response.getHeaders().containsKey("Set-Cookie")) { + if (response.getHeaders().containsHeader("Set-Cookie")) { for (String cookie : response.getHeaders().get("Set-Cookie")) { headers.add("Cookie", cookie); } From c5a9efc0eca6d9371f37bd4187ee779fbaa3d28f Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 08:55:53 +0300 Subject: [PATCH 10/58] Migrate MediaType.sortByQualityValue to local implementation for Spring 7 Spring 7 removed MediaType.sortByQualityValue() and QUALITY_VALUE_COMPARATOR that were deprecated in Spring 6. Copy the sorting logic into a new MediaTypeComparators utility class to preserve content negotiation behavior that respects client quality value preferences from Accept headers. https://github.com/spring-projects/spring-framework/blob/9f431e2eac1b6d8d5ca385d0cc367bac94dd37e7/spring-web/src/main/java/org/springframework/http/MediaType.java#L927-L965 # Conflicts: # server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/error/DefaultOAuth2ExceptionRenderer.java # server/src/main/java/org/cloudfoundry/identity/uaa/web/ConvertingExceptionView.java --- .../uaa/util/MediaTypeComparators.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java new file mode 100644 index 00000000000..dcfa0af28ed --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java @@ -0,0 +1,54 @@ +package org.cloudfoundry.identity.uaa.util; + +import org.springframework.http.MediaType; + +import java.util.Comparator; + +/** + * Comparators for sorting MediaType instances. + * Replaces the deprecated MediaType.QUALITY_VALUE_COMPARATOR and MediaType.sortByQualityValue + * that were removed in Spring Framework 7. + */ +public final class MediaTypeComparators { + + private MediaTypeComparators() {} + + /** + * Comparator for sorting media types by quality value. + * This implementation preserves the exact sorting logic from Spring Framework 6's + * MediaType.QUALITY_VALUE_COMPARATOR to ensure content negotiation works correctly. + */ + public static final Comparator BY_QUALITY_VALUE = (mediaType1, mediaType2) -> { + double quality1 = mediaType1.getQualityValue(); + double quality2 = mediaType2.getQualityValue(); + int qualityComparison = Double.compare(quality2, quality1); + if (qualityComparison != 0) { + return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 + } + else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/* + return 1; + } + else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */* + return -1; + } + else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html + return 0; + } + else { // mediaType1.getType().equals(mediaType2.getType()) + if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic + return 1; + } + else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/* + return -1; + } + else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave + return 0; + } + else { + int paramsSize1 = mediaType1.getParameters().size(); + int paramsSize2 = mediaType2.getParameters().size(); + return Integer.compare(paramsSize2, paramsSize1); // audio/basic;level=1 < audio/basic + } + } + }; +} From 9d24cf607d344ae337a4cebcfeeae7ddd3d6042d Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 22 Apr 2026 14:07:20 +0300 Subject: [PATCH 11/58] Address review feedback on MediaType migration - Rename MediaTypeComparators to MediaTypeUtils for better semantics - Add sortByQualityValue() utility method to handle immutable lists - Fix usages to create mutable copies before sorting - Remove unnecessary if-else in ConvertingExceptionView - Add comprehensive unit tests including parameterized tests # Conflicts: # server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtils.java # server/src/test/java/org/cloudfoundry/identity/uaa/util/MediaTypeUtilsTest.java --- .../uaa/util/MediaTypeComparators.java | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java deleted file mode 100644 index dcfa0af28ed..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/MediaTypeComparators.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.cloudfoundry.identity.uaa.util; - -import org.springframework.http.MediaType; - -import java.util.Comparator; - -/** - * Comparators for sorting MediaType instances. - * Replaces the deprecated MediaType.QUALITY_VALUE_COMPARATOR and MediaType.sortByQualityValue - * that were removed in Spring Framework 7. - */ -public final class MediaTypeComparators { - - private MediaTypeComparators() {} - - /** - * Comparator for sorting media types by quality value. - * This implementation preserves the exact sorting logic from Spring Framework 6's - * MediaType.QUALITY_VALUE_COMPARATOR to ensure content negotiation works correctly. - */ - public static final Comparator BY_QUALITY_VALUE = (mediaType1, mediaType2) -> { - double quality1 = mediaType1.getQualityValue(); - double quality2 = mediaType2.getQualityValue(); - int qualityComparison = Double.compare(quality2, quality1); - if (qualityComparison != 0) { - return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 - } - else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/* - return 1; - } - else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */* - return -1; - } - else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html - return 0; - } - else { // mediaType1.getType().equals(mediaType2.getType()) - if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic - return 1; - } - else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/* - return -1; - } - else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave - return 0; - } - else { - int paramsSize1 = mediaType1.getParameters().size(); - int paramsSize2 = mediaType2.getParameters().size(); - return Integer.compare(paramsSize2, paramsSize1); // audio/basic;level=1 < audio/basic - } - } - }; -} From 3d6ae67aa2cc6e75fd1bc97f4912b08b9dc7984f Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 23 Apr 2026 20:57:10 +0300 Subject: [PATCH 12/58] Migrate SAML API to Spring Security 7 Replace getAssertingPartyDetails() with getAssertingPartyMetadata() Replace withRelyingPartyRegistration() with mutate() Replace assertingPartyDetails() with assertingPartyMetadata() in builder Replace RelyingPartyRegistration.AssertingPartyMetadata with AssertingPartyMetadata --- .../BootstrapSamlIdentityProviderData.java | 2 +- .../saml/OpenSaml5AuthenticationProvider.java | 4 ++-- .../saml/OpenSamlVerificationUtils.java | 4 ++-- .../saml/RelyingPartyRegistrationBuilder.java | 2 +- .../SamlIdentityProviderConfigurator.java | 4 ++-- .../UaaDelegatingLogoutSuccessHandler.java | 3 ++- .../UaaRelyingPartyRegistrationResolver.java | 4 ++-- ...elyingPartyRegistrationRepositoryTest.java | 21 ++++++++++--------- ...elyingPartyRegistrationRepositoryTest.java | 13 ++++++------ .../RelyingPartyRegistrationBuilderTest.java | 15 ++++++------- ...earerGrantAuthenticationConverterTest.java | 4 ++-- .../uaa/provider/saml/Saml2TestUtils.java | 4 ++-- ...SamlIdentityProviderConfiguratorTests.java | 2 +- ...PartyRegistrationRepositoryConfigTest.java | 5 +++-- .../provider/saml/TestOpenSamlObjects.java | 12 +++++------ .../saml/TestRelyingPartyRegistrations.java | 6 +++--- ...UaaDelegatingLogoutSuccessHandlerTest.java | 9 ++++---- ...RelyingPartyRegistrationResolverTests.java | 5 +++-- 18 files changed, 63 insertions(+), 56 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java index dec15bf8b8d..639598ced18 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/BootstrapSamlIdentityProviderData.java @@ -185,7 +185,7 @@ public void setIdentityProviders(Map> providers) { IdentityProvider provider = parseSamlProvider(def); if (def.getType() == SamlIdentityProviderDefinition.MetadataLocation.DATA) { RelyingPartyRegistration metadataDelegate = samlConfigurator.getExtendedMetadataDelegate(def); - def.setIdpEntityId(metadataDelegate.getAssertingPartyDetails().getEntityId()); + def.setIdpEntityId(metadataDelegate.getAssertingPartyMetadata().getEntityId()); } IdentityProviderWrapper wrapper = new IdentityProviderWrapper<>(provider); wrapper.setOverride(override == null || override); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java index 3e438781b32..945adc95a8e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProvider.java @@ -242,7 +242,7 @@ public static Converter createDefau result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, message)); } String assertingPartyEntityId = token.getRelyingPartyRegistration() - .getAssertingPartyDetails() + .getAssertingPartyMetadata() .getEntityId(); if (!StringUtils.hasText(issuer) || !issuer.equals(assertingPartyEntityId)) { String message = "Invalid issuer [%s] for SAML response [%s]".formatted(issuer, response.getID()); @@ -595,7 +595,7 @@ private static ValidationContext createValidationContext(AssertionToken assertio } else { recipientList = Set.of(relyingPartyRegistration.getAssertionConsumerServiceLocation()); } - String assertingPartyEntityId = relyingPartyRegistration.getAssertingPartyDetails().getEntityId(); + String assertingPartyEntityId = relyingPartyRegistration.getAssertingPartyMetadata().getEntityId(); Map params = new HashMap<>(); Assertion assertion = assertionToken.getAssertion(); if (!saml2Bearer && assertionContainsInResponseTo(assertion)) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java index 5043a81e7a1..0a28068b90f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlVerificationUtils.java @@ -65,11 +65,11 @@ static VerifierPartial verifySignature(StatusResponseType object, RelyingPartyRe static SignatureTrustEngine trustEngine(RelyingPartyRegistration registration) { Set credentials = new HashSet<>(); - Collection keys = registration.getAssertingPartyDetails().getVerificationX509Credentials(); + Collection keys = registration.getAssertingPartyMetadata().getVerificationX509Credentials(); for (Saml2X509Credential key : keys) { BasicX509Credential cred = new BasicX509Credential(key.getCertificate()); cred.setUsageType(UsageType.SIGNING); - cred.setEntityId(registration.getAssertingPartyDetails().getEntityId()); + cred.setEntityId(registration.getAssertingPartyMetadata().getEntityId()); credentials.add(cred); } CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java index be03059b82e..efabfb2742c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilder.java @@ -87,7 +87,7 @@ public static RelyingPartyRegistration buildRelyingPartyRegistration(Params buil }) // alter the default value of the APs wantAuthnRequestsSigned, // to reflect the UAA configured desire to always sign/or-not the AuthnRequest - .assertingPartyDetails(details -> { + .assertingPartyMetadata(details -> { details.wantAuthnRequestsSigned(params.requestSigned); details.signingAlgorithms(alg -> { alg.clear(); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java index d6db5c569d4..dcc8f07c25d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java @@ -87,7 +87,7 @@ public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityPr } SamlIdentityProviderDefinition clone = providerDefinition.clone(); added = getExtendedMetadataDelegate(clone); - String entityIDToBeAdded = added.getAssertingPartyDetails().getEntityId(); + String entityIDToBeAdded = added.getAssertingPartyMetadata().getEntityId(); if (!hasText(entityIDToBeAdded)) { throw new IllegalStateException("Emtpy entityID for SAML provider with zoneId:" + providerDefinition.getZoneId() + " and origin:" + providerDefinition.getIdpEntityAlias()); } @@ -100,7 +100,7 @@ public synchronized String validateSamlIdentityProviderDefinition(SamlIdentityPr continue; } RelyingPartyRegistration existingProvider = getExtendedMetadataDelegate(existing); - if (entityIDToBeAdded.equals(existingProvider.getAssertingPartyDetails().getEntityId()) && !existing.getUniqueAlias().equals(clone.getUniqueAlias())) { + if (entityIDToBeAdded.equals(existingProvider.getAssertingPartyMetadata().getEntityId()) && !existing.getUniqueAlias().equals(clone.getUniqueAlias())) { entityIDexists = true; break; } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java index 77dbf522017..f57c11a9521 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandler.java @@ -6,6 +6,7 @@ import org.cloudfoundry.identity.uaa.provider.oauth.ExternalOAuthLogoutSuccessHandler; import org.springframework.security.core.Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler; @@ -94,7 +95,7 @@ private boolean shouldPerformSamlRelyingPartyLogout(HttpServletRequest request, return false; } - String singleLogoutServiceLocation = Optional.ofNullable(registration.getAssertingPartyDetails()).map(RelyingPartyRegistration.AssertingPartyDetails::getSingleLogoutServiceLocation).orElse(null); + String singleLogoutServiceLocation = Optional.ofNullable(registration.getAssertingPartyMetadata()).map(AssertingPartyMetadata::getSingleLogoutServiceLocation).orElse(null); return singleLogoutServiceLocation != null; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolver.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolver.java index 08e102eb217..75b138e46b6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolver.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolver.java @@ -91,7 +91,7 @@ public RelyingPartyRegistration resolve(HttpServletRequest request, String relyi String assertionConsumerServiceLocation = templateResolver.apply(relyingPartyRegistration.getAssertionConsumerServiceLocation()); String singleLogoutServiceLocation = templateResolver.apply(relyingPartyRegistration.getSingleLogoutServiceLocation()); String singleLogoutServiceResponseLocation = templateResolver.apply(relyingPartyRegistration.getSingleLogoutServiceResponseLocation()); - return RelyingPartyRegistration.withRelyingPartyRegistration(relyingPartyRegistration).entityId(relyingPartyEntityId).assertionConsumerServiceLocation(assertionConsumerServiceLocation).singleLogoutServiceLocation(singleLogoutServiceLocation).singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation).build(); + return relyingPartyRegistration.mutate().entityId(relyingPartyEntityId).assertionConsumerServiceLocation(assertionConsumerServiceLocation).singleLogoutServiceLocation(singleLogoutServiceLocation).singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation).build(); } } } @@ -130,7 +130,7 @@ private static String resolveUrlTemplate(String template, String baseUrl, Relyin } private static Map constructUriVariables(String baseUrl, RelyingPartyRegistration relyingParty) { - String entityId = relyingParty.getAssertingPartyDetails().getEntityId(); + String entityId = relyingParty.getAssertingPartyMetadata().getEntityId(); String registrationId = relyingParty.getRegistrationId(); Map uriVariables = new HashMap<>(); UriComponents uriComponents = UriComponentsBuilder.fromUriString(baseUrl).replaceQuery(null).fragment(null).build(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java index 64cdc94eff9..a26e27c7532 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfiguratorRelyingPartyRegistrationRepositoryTest.java @@ -16,6 +16,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.util.FileCopyUtils; @@ -119,8 +120,8 @@ void findByRegistrationIdWithMultipleInDb() { .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", AssertingPartyMetadata::getEntityId); } @Test @@ -167,8 +168,8 @@ void buildsCorrectRegistrationWhenMetadataXmlIsStored() { .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", AssertingPartyMetadata::getEntityId); } @Test @@ -222,8 +223,8 @@ void buildsCorrectRegistrationWhenMetadataLocationIsStored() { .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", AssertingPartyMetadata::getEntityId); } @Test @@ -271,10 +272,10 @@ void buildsCorrectRegistrationWhenZoneIdIsStored() { .returns("{baseUrl}/saml/SSO/alias/zoneDomain.entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/zoneDomain.entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", AssertingPartyMetadata::getEntityId) // signature algorithm defaults to SHA256 - .extracting(RelyingPartyRegistration.AssertingPartyDetails::getSigningAlgorithms) + .extracting(AssertingPartyMetadata::getSigningAlgorithms) .isEqualTo(List.of(ALGO_ID_SIGNATURE_RSA_SHA256)); } @@ -347,7 +348,7 @@ void withSha512SignatureAlgorithm() { when(configurator.getIdentityProviderDefinitionsForZone(identityZone)).thenReturn(List.of(definition)); RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); - assertThat(registration.getAssertingPartyDetails().getSigningAlgorithms()) + assertThat(registration.getAssertingPartyMetadata().getSigningAlgorithms()) .hasSize(1) .first() .isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA512); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java index 86d370329bc..40f3543f6be 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/DefaultRelyingPartyRegistrationRepositoryTest.java @@ -12,6 +12,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import java.security.Security; @@ -81,8 +82,8 @@ void findByRegistrationId() { .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("entityId", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("entityId", AssertingPartyMetadata::getEntityId); } @Test @@ -104,10 +105,10 @@ void findByRegistrationIdForZone() { .returns("{baseUrl}/saml/SSO/alias/testzone.entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/testzone.entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("testzone.entityId", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("testzone.entityId", AssertingPartyMetadata::getEntityId) // signature algorithm defaults to SHA256 - .extracting(RelyingPartyRegistration.AssertingPartyDetails::getSigningAlgorithms) + .extracting(AssertingPartyMetadata::getSigningAlgorithms) .isEqualTo(List.of(ALGO_ID_SIGNATURE_RSA_SHA256)); } @@ -204,7 +205,7 @@ void withSha512SignatureAlgorithm() { when(identityZoneConfig.getSamlConfig()).thenReturn(samlConfig); RelyingPartyRegistration registration = repository.findByRegistrationId(REGISTRATION_ID); - assertThat(registration.getAssertingPartyDetails().getSigningAlgorithms()) + assertThat(registration.getAssertingPartyMetadata().getSigningAlgorithms()) .hasSize(1) .first() .isEqualTo(ALGO_ID_SIGNATURE_RSA_SHA512); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java index 28a4f962970..163323162dd 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/RelyingPartyRegistrationBuilderTest.java @@ -7,6 +7,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.util.FileCopyUtils; @@ -54,9 +55,9 @@ void buildsRelyingPartyRegistrationFromLocation() { .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) - .returns(true, RelyingPartyRegistration.AssertingPartyDetails::getWantAuthnRequestsSigned); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", AssertingPartyMetadata::getEntityId) + .returns(true, AssertingPartyMetadata::getWantAuthnRequestsSigned); } @Test @@ -83,9 +84,9 @@ void buildsRelyingPartyRegistrationFromXML() { .returns("{baseUrl}/saml/SSO/alias/entityIdAlias", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityIdAlias", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", RelyingPartyRegistration.AssertingPartyDetails::getEntityId) - .returns(false, RelyingPartyRegistration.AssertingPartyDetails::getWantAuthnRequestsSigned); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("https://idp-saml.ua3.int/simplesaml/saml2/idp/metadata.php", AssertingPartyMetadata::getEntityId) + .returns(false, AssertingPartyMetadata::getWantAuthnRequestsSigned); } @Test @@ -114,7 +115,7 @@ void withCredentials() { .extracting(Saml2X509Credential::getCertificate) .containsOnly(x509Certificate1()); - assertThat(registration.getAssertingPartyDetails().getSigningAlgorithms()) + assertThat(registration.getAssertingPartyMetadata().getSigningAlgorithms()) .hasSize(2) .containsOnly(SignatureAlgorithm.SHA512.getSignatureAlgorithmURI(), SignatureAlgorithm.SHA256.getSignatureAlgorithmURI()); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java index d8a57f3459c..205aaf31290 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java @@ -557,11 +557,11 @@ private RelyingPartyRegistration.Builder registration() { return TestRelyingPartyRegistrations.noCredentials() .entityId(RELYING_PARTY_ENTITY_ID) .assertionConsumerServiceLocation(DESTINATION) - .assertingPartyDetails(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); + .assertingPartyMetadata(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); } private RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { - return builder.assertingPartyDetails(party -> party + return builder.assertingPartyMetadata(party -> party .verificationX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java index 1cfe92c6b9a..6ea0d9860e7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java @@ -244,11 +244,11 @@ public static RelyingPartyRegistration.Builder registration() { return TestRelyingPartyRegistrations.noCredentials() .entityId(RELYING_PARTY_ENTITY_ID) .assertionConsumerServiceLocation(DESTINATION) - .assertingPartyDetails(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); + .assertingPartyMetadata(party -> party.entityId(ASSERTING_PARTY_ENTITY_ID)); } public static RelyingPartyRegistration.Builder verifying(RelyingPartyRegistration.Builder builder) { - return builder.assertingPartyDetails(party -> party + return builder.assertingPartyMetadata(party -> party .verificationX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java index 0e1344b17b4..ed887219734 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfiguratorTests.java @@ -147,7 +147,7 @@ void getEntityID() { } case "simplesamlphp-url": { RelyingPartyRegistration extendedMetadataDelegate = configurator.getExtendedMetadataDelegate(def); - assertThat(extendedMetadataDelegate.getAssertingPartyDetails().getEntityId()).isEqualTo("http://simplesamlphp.somewhere.com/saml2/idp/metadata.php"); + assertThat(extendedMetadataDelegate.getAssertingPartyMetadata().getEntityId()).isEqualTo("http://simplesamlphp.somewhere.com/saml2/idp/metadata.php"); break; } default: diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java index b733e64b3eb..3928d4f7cdc 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRelyingPartyRegistrationRepositoryConfigTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; @@ -85,7 +86,7 @@ void buildsRegistrationForExample() { .returns("{baseUrl}/saml/SSO/alias/entityId", RelyingPartyRegistration::getAssertionConsumerServiceLocation) .returns("{baseUrl}/saml/SingleLogout/alias/entityId", RelyingPartyRegistration::getSingleLogoutServiceResponseLocation) // from xml - .extracting(RelyingPartyRegistration::getAssertingPartyDetails) - .returns("entityId", RelyingPartyRegistration.AssertingPartyDetails::getEntityId); + .extracting(RelyingPartyRegistration::getAssertingPartyMetadata) + .returns("entityId", AssertingPartyMetadata::getEntityId); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java index 36720470fab..380a9d41d82 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java @@ -503,7 +503,7 @@ public static LogoutRequest assertingPartyLogoutRequest(RelyingPartyRegistration logoutRequest.setNameID(nameId); IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + issuer.setValue(registration.getAssertingPartyMetadata().getEntityId()); logoutRequest.setIssuer(issuer); logoutRequest.setDestination(registration.getSingleLogoutServiceLocation()); return logoutRequest; @@ -517,7 +517,7 @@ public static LogoutRequest assertingPartyLogoutRequestNameIdInEncryptedId(Relyi NameID nameId = nameIdBuilder.buildObject(); nameId.setValue("user"); logoutRequest.setNameID(null); - Saml2X509Credential credential = registration.getAssertingPartyDetails() + Saml2X509Credential credential = registration.getAssertingPartyMetadata() .getEncryptionX509Credentials() .iterator() .next(); @@ -525,7 +525,7 @@ public static LogoutRequest assertingPartyLogoutRequestNameIdInEncryptedId(Relyi logoutRequest.setEncryptedID(encrypted); IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + issuer.setValue(registration.getAssertingPartyMetadata().getEntityId()); logoutRequest.setIssuer(issuer); logoutRequest.setDestination(registration.getSingleLogoutServiceLocation()); return logoutRequest; @@ -544,7 +544,7 @@ public static LogoutResponse assertingPartyLogoutResponse(RelyingPartyRegistrati logoutResponse.setStatus(status); IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + issuer.setValue(registration.getAssertingPartyMetadata().getEntityId()); logoutResponse.setIssuer(issuer); logoutResponse.setDestination(registration.getSingleLogoutServiceResponseLocation()); return logoutResponse; @@ -561,9 +561,9 @@ public static LogoutRequest relyingPartyLogoutRequest(RelyingPartyRegistration r logoutRequest.setNameID(nameId); IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(); - issuer.setValue(registration.getAssertingPartyDetails().getEntityId()); + issuer.setValue(registration.getAssertingPartyMetadata().getEntityId()); logoutRequest.setIssuer(issuer); - logoutRequest.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation()); + logoutRequest.setDestination(registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation()); return logoutRequest; } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java index 2bfe428f869..7719263ecf1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java @@ -48,7 +48,7 @@ public static RelyingPartyRegistration.Builder relyingPartyRegistration() { .nameIdFormat("format") .assertionConsumerServiceLocation(assertionConsumerServiceLocation) .singleLogoutServiceLocation(singleLogoutServiceLocation) - .assertingPartyDetails(c -> c.entityId(apEntityId).singleSignOnServiceLocation(singleSignOnServiceLocation)) + .assertingPartyMetadata(c -> c.entityId(apEntityId).singleSignOnServiceLocation(singleSignOnServiceLocation)) .signingX509Credentials(c -> c.add(signingCredential)) .decryptionX509Credentials(c -> c.add(verificationCertificate)); } @@ -59,7 +59,7 @@ public static RelyingPartyRegistration.Builder noCredentials() { .singleLogoutServiceLocation("https://rp.example.org/logout/saml2/request") .singleLogoutServiceResponseLocation("https://rp.example.org/logout/saml2/response") .assertionConsumerServiceLocation("https://rp.example.org/acs") - .assertingPartyDetails(party -> party.entityId("ap-entity-id") + .assertingPartyMetadata(party -> party.entityId("ap-entity-id") .singleSignOnServiceLocation("https://ap.example.org/sso") .singleLogoutServiceLocation("https://ap.example.org/logout/saml2/request") .singleLogoutServiceResponseLocation("https://ap.example.org/logout/saml2/response")); @@ -69,7 +69,7 @@ public static RelyingPartyRegistration.Builder full() { return noCredentials() .signingX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential())) .decryptionX509Credentials(c -> c.add(TestSaml2X509Credentials.relyingPartyDecryptingCredential())) - .assertingPartyDetails(party -> party.verificationX509Credentials( + .assertingPartyMetadata(party -> party.verificationX509Credentials( c -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java index 92518cd3ad9..e847f2a8490 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaDelegatingLogoutSuccessHandlerTest.java @@ -10,6 +10,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.core.Authentication; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler; @@ -82,8 +83,8 @@ void shouldPerformSamlRelyingPartyLogout() throws ServletException, IOException when(mockPrincipal.getRelyingPartyRegistrationId()).thenReturn(REG_ID); var mockRegistration = mock(RelyingPartyRegistration.class); when(relyingPartyRegistrationResolver.resolve(any(), eq(REG_ID))).thenReturn(mockRegistration); - var mockAssertingPartyDetails = mock(RelyingPartyRegistration.AssertingPartyDetails.class); - when(mockRegistration.getAssertingPartyDetails()).thenReturn(mockAssertingPartyDetails); + var mockAssertingPartyDetails = mock(AssertingPartyMetadata.class); + when(mockRegistration.getAssertingPartyMetadata()).thenReturn(mockAssertingPartyDetails); when(mockAssertingPartyDetails.getSingleLogoutServiceLocation()).thenReturn(URL); logoutSuccessHandler.onLogoutSuccess(request, response, authentication); @@ -140,8 +141,8 @@ void nullSingleLogoutServiceLocationFallsThruToZoneAwareWhitelistLogoutHandler() when(mockPrincipal.getRelyingPartyRegistrationId()).thenReturn(REG_ID); var mockRegistration = mock(RelyingPartyRegistration.class); when(relyingPartyRegistrationResolver.resolve(any(), eq(REG_ID))).thenReturn(mockRegistration); - var mockAssertingPartyDetails = mock(RelyingPartyRegistration.AssertingPartyDetails.class); - when(mockRegistration.getAssertingPartyDetails()).thenReturn(mockAssertingPartyDetails); + var mockAssertingPartyDetails = mock(AssertingPartyMetadata.class); + when(mockRegistration.getAssertingPartyMetadata()).thenReturn(mockAssertingPartyDetails); logoutSuccessHandler.onLogoutSuccess(request, response, authentication); verifyCorrectOnLogoutSuccessCalled(false, false, true); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java index 47f98c2c8b1..e91cc72c9b4 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java @@ -24,6 +24,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; @@ -104,10 +105,10 @@ void resolveWhenRequestIsWithValiddSamlResponseFromSimplySamlButNoTrust() { void resolveWhenRequestIsWithValiddSamlResponseFromSimplySaml() { MockHttpServletRequest request = new MockHttpServletRequest(); RelyingPartyRegistration newMock = mock(RelyingPartyRegistration.class); - RelyingPartyRegistration.AssertingPartyDetails details = mock(RelyingPartyRegistration.AssertingPartyDetails.class); + AssertingPartyMetadata details = mock(AssertingPartyMetadata.class); doReturn("simpleID").when(newMock).getRegistrationId(); doReturn("simpleEndityID").when(newMock).getEntityId(); - doReturn(details).when(newMock).getAssertingPartyDetails(); + doReturn(details).when(newMock).getAssertingPartyMetadata(); doReturn("simpleEndityID").when(details).getEntityId(); doReturn("sso").when(details).getSingleSignOnServiceLocation(); doReturn("acs").when(newMock).getAssertionConsumerServiceLocation(); From 4eb1c1614dc2184d53fabd2f007d7acef1d86513 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Fri, 24 Apr 2026 16:26:29 +0300 Subject: [PATCH 13/58] Migrate Authorization Manager API to Spring Security 7 Replace check() method with authorize() in production code and tests Update parameter and return types to use AuthorizationResult Add jspecify nullable annotations --- .../uaa/web/AuthorizationManagersUtils.java | 14 ++++--- .../web/SelfCheckAuthorizationManager.java | 6 ++- .../web/AuthorizationManagersUtilsTests.java | 42 +++++++++---------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtils.java index b7d2f6d71a8..58c70b9c121 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtils.java @@ -10,10 +10,12 @@ import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InsufficientScopeException; import org.cloudfoundry.identity.uaa.oauth.provider.expression.OAuth2ExpressionUtils; import org.cloudfoundry.identity.uaa.security.ContextSensitiveOAuth2SecurityExpressionMethods; +import org.jspecify.annotations.Nullable; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.web.access.intercept.RequestAuthorizationContext; @@ -44,13 +46,13 @@ public static class AnyOfAuthorizationManager implements AuthorizationManager missingScopes = new LinkedHashSet<>(); @Override - public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext object) { + public AuthorizationResult authorize(Supplier authentication, RequestAuthorizationContext object) { for (var authorizationManager : this.delegateAuthorizationManagers) { - AuthorizationDecision decision = authorizationManager.check(authentication, object); - if (decision != null) { - if (decision.isGranted()) { - return decision; - } else if (decision instanceof ScopeTrackingAuthorizationDecision scopeDecision) { + AuthorizationResult result = authorizationManager.authorize(authentication, object); + if (result != null) { + if (result.isGranted()) { + return result; + } else if (result instanceof ScopeTrackingAuthorizationDecision scopeDecision) { missingScopes.addAll(scopeDecision.getScopes()); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/SelfCheckAuthorizationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/SelfCheckAuthorizationManager.java index 75fd7fcdcc4..b65a21c5b1c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/SelfCheckAuthorizationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/SelfCheckAuthorizationManager.java @@ -4,8 +4,10 @@ import jakarta.servlet.http.HttpServletRequest; import org.cloudfoundry.identity.uaa.security.IsSelfCheck; +import org.jspecify.annotations.Nullable; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.web.access.intercept.RequestAuthorizationContext; @@ -63,12 +65,12 @@ private SelfCheckAuthorizationManager(CheckType type, IsSelfCheck selfCheck, int } @Override - public void verify(Supplier authentication, RequestAuthorizationContext context) { + public void verify(Supplier authentication, RequestAuthorizationContext context) { AuthorizationManager.super.verify(authentication, context); } @Override - public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext context) { + public AuthorizationResult authorize(Supplier authentication, RequestAuthorizationContext context) { HttpServletRequest request = context.getRequest(); switch (type) { case USER -> { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java index 0a8c1785335..9d60b93e629 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java @@ -43,7 +43,7 @@ static void afterAll() { void noAuthenticationManager() { AuthorizationManager authManager = AuthorizationManagersUtils.anyOf(); - var authorizationDecision = authManager.check(() -> FULLY_AUTHENTICATED, null); + var authorizationDecision = authManager.authorize(() -> FULLY_AUTHENTICATED, null); assertThat(authorizationDecision.isGranted()).isFalse(); } @@ -53,11 +53,11 @@ void oneAuthenticationManager() { var granted = granted(); var notGranted = notGranted(); var unknown = unknown(); - assertThat(AuthorizationManagersUtils.anyOf().or(granted).check(() -> FULLY_AUTHENTICATED, null).isGranted()).isTrue(); + assertThat(AuthorizationManagersUtils.anyOf().or(granted).authorize(() -> FULLY_AUTHENTICATED, null).isGranted()).isTrue(); assertThat(granted.called).isTrue(); - assertThat(AuthorizationManagersUtils.anyOf().or(notGranted).check(() -> FULLY_AUTHENTICATED, null).isGranted()).isFalse(); + assertThat(AuthorizationManagersUtils.anyOf().or(notGranted).authorize(() -> FULLY_AUTHENTICATED, null).isGranted()).isFalse(); assertThat(notGranted.called).isTrue(); - assertThat(AuthorizationManagersUtils.anyOf().or(unknown).check(() -> FULLY_AUTHENTICATED, null).isGranted()).isFalse(); + assertThat(AuthorizationManagersUtils.anyOf().or(unknown).authorize(() -> FULLY_AUTHENTICATED, null).isGranted()).isFalse(); assertThat(unknown.called).isTrue(); } @@ -71,7 +71,7 @@ void manyAuthenticationManagers() { .or(granted) .or(unknown); - assertThat(authorizationManager.check(() -> FULLY_AUTHENTICATED, null).isGranted()).isTrue(); + assertThat(authorizationManager.authorize(() -> FULLY_AUTHENTICATED, null).isGranted()).isTrue(); assertThat(notGranted.called).isTrue(); assertThat(granted.called).isTrue(); assertThat(unknown.called).isFalse(); @@ -82,10 +82,10 @@ void anonymous() { AuthorizationManager authManager = AuthorizationManagersUtils.anyOf() .anonymous(); - assertThat(authManager.check(() -> ANONYMOUS, null).isGranted()).isTrue(); - assertThat(authManager.check(() -> NOT_AUTHENTICATED, null).isGranted()).isFalse(); - assertThat(authManager.check(() -> FULLY_AUTHENTICATED, null).isGranted()).isFalse(); - assertThat(authManager.check(() -> REMEMBER_ME, null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> ANONYMOUS, null).isGranted()).isTrue(); + assertThat(authManager.authorize(() -> NOT_AUTHENTICATED, null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> FULLY_AUTHENTICATED, null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> REMEMBER_ME, null).isGranted()).isFalse(); } @Test @@ -93,26 +93,26 @@ void fullyAuthenticated() { AuthorizationManager authManager = AuthorizationManagersUtils.anyOf() .fullyAuthenticated(); - assertThat(authManager.check(() -> ANONYMOUS, null).isGranted()).isFalse(); - assertThat(authManager.check(() -> NOT_AUTHENTICATED, null).isGranted()).isFalse(); - assertThat(authManager.check(() -> FULLY_AUTHENTICATED, null).isGranted()).isTrue(); - assertThat(authManager.check(() -> REMEMBER_ME, null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> ANONYMOUS, null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> NOT_AUTHENTICATED, null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> FULLY_AUTHENTICATED, null).isGranted()).isTrue(); + assertThat(authManager.authorize(() -> REMEMBER_ME, null).isGranted()).isFalse(); } @Test void uaaAdmin() { AuthorizationManager authManager = AuthorizationManagersUtils.anyOf().isUaaAdmin(); - assertThat(authManager.check(() -> withScopes("foo.bar"), null).isGranted()).isFalse(); - assertThat(authManager.check(() -> withScopes("uaa.admin"), null).isGranted()).isTrue(); + assertThat(authManager.authorize(() -> withScopes("foo.bar"), null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> withScopes("uaa.admin"), null).isGranted()).isTrue(); } @Test void hasScope() { AuthorizationManager authManager = AuthorizationManagersUtils.anyOf().hasScope("foo.bar"); - assertThat(authManager.check(() -> withScopes("uaa.admin"), null).isGranted()).isFalse(); - assertThat(authManager.check(() -> withScopes("uaa.admin", "foo.bar"), null).isGranted()).isTrue(); + assertThat(authManager.authorize(() -> withScopes("uaa.admin"), null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> withScopes("uaa.admin", "foo.bar"), null).isGranted()).isTrue(); } @Test @@ -120,7 +120,7 @@ void throwOnError() { AuthorizationManager authManager = AuthorizationManagersUtils.anyOf(true).hasScope("foo.bar"); assertThatThrownBy( - () -> {authManager.check(() -> withScopes("uaa.admin"), null);} + () -> {authManager.authorize(() -> withScopes("uaa.admin"), null);} ).getCause().isInstanceOf(InsufficientScopeException.class); } @@ -131,8 +131,8 @@ void zoneAdmin() { IdentityZoneHolder.set(ModelTestUtils.identityZone("someZoneId", "some-domain")); - assertThat(authManager.check(() -> withScopes("foo.bar"), null).isGranted()).isFalse(); - assertThat(authManager.check(() -> withScopes("zones.someZoneId.admin"), null).isGranted()).isTrue(); + assertThat(authManager.authorize(() -> withScopes("foo.bar"), null).isGranted()).isFalse(); + assertThat(authManager.authorize(() -> withScopes("zones.someZoneId.admin"), null).isGranted()).isTrue(); } static class TestAuthManager implements AuthorizationManager { @@ -161,7 +161,7 @@ public static TestAuthManager unknown() { } @Override - public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext object) { + public AuthorizationDecision authorize(Supplier authentication, RequestAuthorizationContext object) { called = true; return authorizationDecision; } From 94b3d169f57925320a64999b985662d94a21ca6e Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Fri, 24 Apr 2026 00:19:07 +0300 Subject: [PATCH 14/58] Remove deprecated setIgnoreDefaultModelOnRedirect call This method was removed in Spring Framework 6.0 without replacement. The default model is now always ignored on redirect. This requires changes to some controllers where model.addAttribute() was used that now won't be delegated. We need to use redirectAttributes.addAttribute(). # Conflicts: # server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java # Conflicts: # server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java # server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java --- .../org/cloudfoundry/identity/uaa/web/WebConfig.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java index 33d91c067a9..6492f11b50e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java @@ -1,14 +1,11 @@ package org.cloudfoundry.identity.uaa.web; -import jakarta.annotation.PostConstruct; import org.cloudfoundry.identity.uaa.authentication.manager.AutologinRequestConverter; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import java.util.List; @@ -25,12 +22,4 @@ class WebConfig implements WebMvcConfigurer { public void extendMessageConverters(List> converters) { converters.add(new AutologinRequestConverter()); } - - @Autowired - private RequestMappingHandlerAdapter requestMappingHandlerAdapter; - - @PostConstruct - public void init() { - requestMappingHandlerAdapter.setIgnoreDefaultModelOnRedirect(false); - } } From cd6b7d175acbe4ef12667c0fb3871fefcbfddcc1 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 00:05:49 +0300 Subject: [PATCH 15/58] Fix Spring Boot 4 web server factory package relocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spring Boot 4 reorganized web server factory classes into dedicated modules: - ConfigurableServletWebServerFactory: org.springframework.boot.web.servlet.server → org.springframework.boot.web.server.servlet - TomcatServletWebServerFactory: org.springframework.boot.web.embedded.tomcat → org.springframework.boot.tomcat.servlet - ErrorPage: org.springframework.boot.web.server → org.springframework.boot.web.error - addAdditionalTomcatConnectors() → addAdditionalConnectors() Updated production code and tests in uaa module. --- .../experimental/boot/UaaBootConfiguration.java | 2 +- .../experimental/boot/UaaBootServerCustomizer.java | 6 +++--- .../experimental/boot/UaaBootServerCustomizerTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootConfiguration.java b/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootConfiguration.java index 6e1f53088af..cdace122df6 100644 --- a/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootConfiguration.java +++ b/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootConfiguration.java @@ -11,7 +11,7 @@ import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletContextInitializer; -import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; diff --git a/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizer.java b/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizer.java index b78de70f4bc..00e79fe0937 100644 --- a/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizer.java +++ b/uaa/src/main/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizer.java @@ -4,8 +4,8 @@ import org.apache.catalina.valves.ErrorReportValve; import org.apache.coyote.http11.Http11NioProtocol; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; -import org.springframework.boot.web.server.ErrorPage; +import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; +import org.springframework.boot.web.error.ErrorPage; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.http.HttpStatus; import org.springframework.security.web.firewall.RequestRejectedException; @@ -37,7 +37,7 @@ public void customize(TomcatServletWebServerFactory factory) { factory.addEngineValves(getErrorReportValve()); if (this.serverHttp.port() > 0) { - factory.addAdditionalTomcatConnectors( + factory.addAdditionalConnectors( getHttpPort( this.serverHttp ) diff --git a/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java b/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java index 63face84a86..7ba18a6540b 100644 --- a/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java @@ -10,7 +10,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; @@ -47,7 +47,7 @@ void customizerAddedReportValve() { void localhostConnectorAdded() throws UnknownHostException { ArgumentCaptor captor = ArgumentCaptor.forClass(TomcatServletWebServerFactory.class); Mockito.verify(customizer, Mockito.atMostOnce()).customize(captor.capture()); - List connectors = captor.getValue().getAdditionalTomcatConnectors(); + List connectors = captor.getValue().getAdditionalConnectors(); assertThat(connectors).isNotEmpty(); assertThat(connectors).hasSize(1); Connector httpConnector = connectors.getFirst(); From 3f6e27bd135258b6675e66c1a0dcc7c462fc6927 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 00:08:58 +0300 Subject: [PATCH 16/58] Fix Spring Security 7 AuthorizationManager jspecify annotations in test Spring Security 7's AuthorizationManager.authorize() now uses jspecify @Nullable annotations on the Authentication parameter: authorize(Supplier, T) Updated TestAuthManager inner class in AuthorizationManagersUtilsTests to match the new signature. --- .../identity/uaa/web/AuthorizationManagersUtilsTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java index 9d60b93e629..09c422e5eaf 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/AuthorizationManagersUtilsTests.java @@ -6,6 +6,7 @@ import org.cloudfoundry.identity.uaa.test.ModelTestUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -161,7 +162,7 @@ public static TestAuthManager unknown() { } @Override - public AuthorizationDecision authorize(Supplier authentication, RequestAuthorizationContext object) { + public AuthorizationDecision authorize(Supplier authentication, RequestAuthorizationContext object) { called = true; return authorizationDecision; } From 5a6376101218c1a0401b422d50bde672191df696 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 00:10:33 +0300 Subject: [PATCH 17/58] Fix Spring Security 7 SecurityBuilder.build() signature change Spring Security 7's SecurityBuilder.build() no longer throws Exception. Removed throws Exception clause from all build() method overrides in ClientDetailsServiceBuilder and its anonymous inner classes. InMemoryClientDetailsServiceBuilder inherits the corrected signature. --- .../oauth/provider/token/ClientDetailsServiceBuilder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/ClientDetailsServiceBuilder.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/ClientDetailsServiceBuilder.java index 97199d22e5b..38990099265 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/ClientDetailsServiceBuilder.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/ClientDetailsServiceBuilder.java @@ -25,15 +25,15 @@ public class ClientDetailsServiceBuilder clientBuilders = new ArrayList<>(); - public InMemoryClientDetailsServiceBuilder inMemory() throws Exception { + public InMemoryClientDetailsServiceBuilder inMemory() { return new InMemoryClientDetailsServiceBuilder(); } @SuppressWarnings("rawtypes") - public ClientDetailsServiceBuilder clients(final ClientDetailsService clientDetailsService) throws Exception { + public ClientDetailsServiceBuilder clients(final ClientDetailsService clientDetailsService) { return new ClientDetailsServiceBuilder() { @Override - public ClientDetailsService build() throws Exception { + public ClientDetailsService build() { return clientDetailsService; } }; @@ -46,7 +46,7 @@ public ClientBuilder withClient(String clientId) { } @Override - public ClientDetailsService build() throws Exception { + public ClientDetailsService build() { for (ClientBuilder clientDetailsBldr : clientBuilders) { addClient(clientDetailsBldr.clientId, clientDetailsBldr.build()); } From 2507f9928dcdde1c58dbe4f0309cdedab4cd8e50 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 00:12:29 +0300 Subject: [PATCH 18/58] Fix AbstractAuthenticationToken constructor ambiguity Spring Security 7 added a new AbstractAuthenticationToken constructor, causing ambiguity when passing null to super(): - AbstractAuthenticationToken(Collection) - AbstractAuthenticationToken(AbstractAuthenticationBuilder) Cast null to Collection to resolve ambiguity in TestAuthentication inner classes. --- .../provider/token/AbstractDefaultTokenServicesTests.java | 3 ++- .../identity/uaa/oauth/provider/token/TokenStoreBaseTests.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/AbstractDefaultTokenServicesTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/AbstractDefaultTokenServicesTests.java index e799cc0a280..2f39560560e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/AbstractDefaultTokenServicesTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/AbstractDefaultTokenServicesTests.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import java.io.Serial; import java.util.Arrays; @@ -244,7 +245,7 @@ protected static class TestAuthentication extends AbstractAuthenticationToken { private final String principal; public TestAuthentication(String name, boolean authenticated) { - super(null); + super((Collection) null); setAuthenticated(authenticated); this.principal = name; } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/TokenStoreBaseTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/TokenStoreBaseTests.java index d09b54713f8..0481697a568 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/TokenStoreBaseTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/provider/token/TokenStoreBaseTests.java @@ -10,6 +10,7 @@ import org.cloudfoundry.identity.uaa.oauth.provider.RequestTokenFactory; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import java.io.Serial; import java.util.Collection; @@ -203,7 +204,7 @@ protected static class TestAuthentication extends AbstractAuthenticationToken { private final String principal; public TestAuthentication(String name, boolean authenticated) { - super(null); + super((Collection) null); setAuthenticated(authenticated); this.principal = name; } From 45ce4391f9ef157da259eab416c89eef21634523 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 00:25:02 +0300 Subject: [PATCH 19/58] Migrate @MockBean and @SpyBean to Spring Boot 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spring Boot 4 removed @MockBean and @SpyBean in favor of Spring Framework's @MockitoBean and @MockitoSpyBean annotations. - @SpyBean → @MockitoSpyBean - @MockBean → @MockitoBean - Package: org.springframework.boot.test.mock.mockito → org.springframework.test.context.bean.override.mockito Updated 5 test files in uaa module. **Cannot be done in isolation** - Boot 4 only change. --- .../experimental/boot/UaaBootServerCustomizerTest.java | 4 ++-- .../cloudfoundry/identity/uaa/login/LoginMockMvcTests.java | 4 ++-- .../identity/uaa/login/LoginMockMvcZonePathTests.java | 4 ++-- .../uaa/performance/LoginPagePerformanceMockMvcTest.java | 4 ++-- .../performance/LoginPagePerformanceMockMvcZonePathTest.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java b/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java index 7ba18a6540b..ce290add781 100644 --- a/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; @@ -27,7 +27,7 @@ @TestPropertySource(properties = {"server.http.port = 8081"}) class UaaBootServerCustomizerTest { - @SpyBean + @MockitoSpyBean UaaBootServerCustomizer customizer; @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index cd751453880..acda26c6834 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -51,7 +51,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -201,7 +201,7 @@ public class LoginMockMvcTests { private IdentityZone identityZone; private File originalLimitedModeStatusFile; - @MockBean + @MockitoBean OidcMetadataFetcher oidcMetadataFetcher; @BeforeEach diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java index e4c8c547480..2156249dc80 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java @@ -57,7 +57,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -203,7 +203,7 @@ public class LoginMockMvcZonePathTests { private IdentityZone identityZone; private File originalLimitedModeStatusFile; - @MockBean + @MockitoBean OidcMetadataFetcher oidcMetadataFetcher; @BeforeEach diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcTest.java index 6754ac1aa33..41b0b233bbc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcTest.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.context.SecurityContextHolder; @@ -57,7 +57,7 @@ class LoginPagePerformanceMockMvcTest { private File originalLimitedModeStatusFile; - @MockBean + @MockitoBean OidcMetadataFetcher oidcMetadataFetcher; @BeforeEach diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcZonePathTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcZonePathTest.java index 01033b7d9e4..16971251256 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcZonePathTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/performance/LoginPagePerformanceMockMvcZonePathTest.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.context.SecurityContextHolder; @@ -60,7 +60,7 @@ class LoginPagePerformanceMockMvcZonePathTest { private File originalLimitedModeStatusFile; - @MockBean + @MockitoBean OidcMetadataFetcher oidcMetadataFetcher; @BeforeEach From 4b55e61c2c0ee2938fe0172eb06d92bde0051a96 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 00:33:20 +0300 Subject: [PATCH 20/58] Remove LDAP and Session AutoConfiguration exclusions for Boot 4 In Spring Boot 4, LDAP and Session support became optional starters: - spring-boot-starter-ldap - spring-boot-starter-session-* Since these starters are not included as dependencies, their AutoConfiguration classes don't exist on the classpath and don't need to be excluded. Removed exclusions: - LdapAutoConfiguration - SessionAutoConfiguration **Cannot be done in isolation** - Boot 4 only change. --- .../org/cloudfoundry/identity/uaa/DefaultTestContext.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java index 9019ff5f53e..ca79ba385e0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/DefaultTestContext.java @@ -12,8 +12,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; -import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.annotation.Bean; @@ -58,12 +56,6 @@ webEnvironment = SpringBootTest.WebEnvironment.MOCK ) @SpringJUnitConfig(initializers = {TestPropertyInitializer.class, YamlServletProfileInitializer.class}) -@EnableAutoConfiguration(exclude = { - // Conflicts with UaaJdbcSessionConfig - SessionAutoConfiguration.class, - // Conflicts with LdapSearchAndCompareConfig/LdapSearchAndBindConfig/LdapSimpleBindConfig - LdapAutoConfiguration.class -}) public @interface DefaultTestContext { } From fc32113b515857e9858cd78daf19df50c2bb9dc6 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 01:35:02 +0300 Subject: [PATCH 21/58] Fix empty password authentication for Spring Security 7 Spring Security 7's DaoAuthenticationProvider rejects empty passwords. Handle empty credentials by manually checking password encoding instead of calling super.additionalAuthenticationChecks(). --- .../ClientDetailsAuthenticationProvider.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java index 13b5cfbf5a0..2814bc223cd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java @@ -93,7 +93,20 @@ protected void additionalAuthenticationChecks(UserDetails userDetails, UsernameP error = new BadCredentialsException("Missing credentials"); break; } - super.additionalAuthenticationChecks(uaaClient, authentication); + // Spring Security 7 rejects empty passwords before validation. + // Handle empty credentials by bypassing parent's empty check + if (ObjectUtils.isEmpty(authentication.getCredentials())) { + String presentedPassword = authentication.getCredentials() == null ? "" : authentication.getCredentials().toString(); + String storedPassword = uaaClient.getPassword(); + // Handle {noop} encoded empty password: "{noop}" means empty password with noop encoding + if ("{noop}".equals(storedPassword) && presentedPassword.isEmpty()) { + error = null; + } else if (!getPasswordEncoder().matches(presentedPassword, storedPassword)) { + throw new BadCredentialsException("Bad credentials"); + } + } else { + super.additionalAuthenticationChecks(uaaClient, authentication); + } error = null; break; } catch (AuthenticationException e) { From f6653df88c386a2349870b004f3aeec6233ec5d3 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 01:35:46 +0300 Subject: [PATCH 22/58] Update CSRF redirect URL assertions for Spring Security 7 Spring Security 7 changed LoginUrlAuthenticationEntryPoint to generate relative URLs by default instead of absolute URLs. --- .../uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java index 2b7f127fc94..beea633b157 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java @@ -53,7 +53,7 @@ void handleWhenNotLoggedInAndNoCsrf() throws Exception { assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FOUND); assertThat(ex).isSameAs(request.getAttribute(WebAttributes.ACCESS_DENIED_403)); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getHeader("Location")).isEqualTo("http://localhost/login"); + assertThat(response.getHeader("Location")).isEqualTo("/login"); assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_MOVED_TEMPORARILY); } @@ -74,7 +74,7 @@ void handleWhenNotLoggedIn() throws Exception { assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_FOUND); assertThat(ex).isSameAs(request.getAttribute(WebAttributes.ACCESS_DENIED_403)); assertThat(response.isCommitted()).isTrue(); - assertThat(response.getHeader("Location")).isEqualTo("http://localhost/login"); + assertThat(response.getHeader("Location")).isEqualTo("/login"); assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_MOVED_TEMPORARILY); } From 96257f58d21acad9094b9214ae361024a4b060c3 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Thu, 26 Mar 2026 12:39:22 +0200 Subject: [PATCH 23/58] fix: adapt tests for Spring Framework 7 MockHttpServletResponse changes Spring Framework 7 changes: 1. LoginUrlAuthenticationEntryPoint now generates relative URLs by default instead of absolute URLs. Updated CSRF redirect assertions from 'http://localhost/login' to '/login'. 2. MockHttpServletResponse.getCookie() no longer automatically parses Set-Cookie headers added via addHeader(). Updated cookie expiry test to check Set-Cookie header directly, which is more accurate since that's what browsers receive. --- .../uaa/web/CookieBasedCsrfTokenRepositoryTests.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java index c14f69fca29..75487928681 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java @@ -142,9 +142,15 @@ void saveToken_MakeAnExpiredTokenInResponse_whenNoTokenInRequest() { MockHttpServletResponse response = new MockHttpServletResponse(); repo.saveToken(null, request, response); - Cookie cookie = response.getCookie("X-Uaa-Csrf"); - assertThat(cookie.getMaxAge()).isZero(); - assertThat(cookie.getValue()).isNotEmpty(); + // Spring Framework 7: MockHttpServletResponse.getCookie() no longer parses + // Set-Cookie headers added via addHeader(). Check the header directly instead. + String setCookieHeader = response.getHeader("Set-Cookie"); + assertThat(setCookieHeader).contains("X-Uaa-Csrf="); + // Max-Age=0 or Expires at Unix epoch indicates expired/deleted cookie + assertThat(setCookieHeader).satisfiesAnyOf( + header -> assertThat(header).contains("Max-Age=0"), + header -> assertThat(header).contains("Expires=Thu, 1 Jan 1970") + ); } private MockHttpServletResponse saveTokenAndReturnResponse(boolean isSecure, String protocol) { From 02037590d2b74cc86d6a69be29ce47360d8394e0 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 26 Apr 2026 01:38:40 +0300 Subject: [PATCH 24/58] Fix RelyingPartyRegistration mock to stub mutate() method Spring Security 7's RelyingPartyRegistration.mutate() is called by UaaRelyingPartyRegistrationResolver. Mock the builder chain to prevent NPE. --- .../saml/UaaRelyingPartyRegistrationResolverTests.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java index e91cc72c9b4..dc30ff63129 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/UaaRelyingPartyRegistrationResolverTests.java @@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -105,6 +106,7 @@ void resolveWhenRequestIsWithValiddSamlResponseFromSimplySamlButNoTrust() { void resolveWhenRequestIsWithValiddSamlResponseFromSimplySaml() { MockHttpServletRequest request = new MockHttpServletRequest(); RelyingPartyRegistration newMock = mock(RelyingPartyRegistration.class); + RelyingPartyRegistration.Builder builder = mock(RelyingPartyRegistration.Builder.class); AssertingPartyMetadata details = mock(AssertingPartyMetadata.class); doReturn("simpleID").when(newMock).getRegistrationId(); doReturn("simpleEndityID").when(newMock).getEntityId(); @@ -112,9 +114,17 @@ void resolveWhenRequestIsWithValiddSamlResponseFromSimplySaml() { doReturn("simpleEndityID").when(details).getEntityId(); doReturn("sso").when(details).getSingleSignOnServiceLocation(); doReturn("acs").when(newMock).getAssertionConsumerServiceLocation(); + doReturn("sls").when(newMock).getSingleLogoutServiceLocation(); + doReturn("slsr").when(newMock).getSingleLogoutServiceResponseLocation(); doReturn(mock(Saml2MessageBinding.class)).when(newMock).getSingleLogoutServiceBinding(); doReturn(mock(Saml2MessageBinding.class)).when(details).getSingleSignOnServiceBinding(); doReturn(mock(Saml2MessageBinding.class)).when(newMock).getAssertionConsumerServiceBinding(); + doReturn(builder).when(newMock).mutate(); + doReturn(builder).when(builder).entityId(anyString()); + doReturn(builder).when(builder).assertionConsumerServiceLocation(anyString()); + doReturn(builder).when(builder).singleLogoutServiceLocation(anyString()); + doReturn(builder).when(builder).singleLogoutServiceResponseLocation(anyString()); + doReturn(newMock).when(builder).build(); doReturn(newMock).when(repository).findByRegistrationId("http://uaa-acceptance.cf-app.com/saml-idp"); request.setRequestURI("/some/path/cloudfoundry-saml-login"); request.setMethod("POST"); From 0b8549780257d6b5adacc19b996f38fc9498ea1b Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Mon, 27 Apr 2026 17:07:14 +0300 Subject: [PATCH 25/58] fix(statsd): migrate to Spring Boot 4 WAR deployment dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace providedRuntime(spring-boot-starter-tomcat) with providedRuntime(spring-boot-starter-tomcat-runtime) per the Spring Boot 4 migration guide for external Tomcat WAR deployment. In Spring Boot 4, spring-boot-starter-tomcat transitively pulls in spring-boot-starter which includes spring-boot, spring-context etc., causing those to land in WEB-INF/lib-provided and be missing at runtime on external Tomcat. The new spring-boot-starter-tomcat-runtime artifact contains only the Tomcat embed jars that the container actually provides. Also remove the now-redundant providedCompile(tomcatEmbed) — originally added to provide javax.servlet.* at compile time, but tomcat-embed-core is already available transitively via spring-boot-starter-web. --- gradle/libs.versions.toml | 3 +++ statsd/build.gradle | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7144ff1cfe..4c4a49adaea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -161,13 +161,16 @@ springWebMvc = { module = "org.springframework:spring-webmvc" } # Spring Boot springBootBom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springBoot" } +springBootJdbc = { module = "org.springframework.boot:spring-boot-jdbc" } springBootSql = { module = "org.springframework.boot:spring-boot-sql" } springBootStarter = { module = "org.springframework.boot:spring-boot-starter" } springBootStarterLog4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2" } springBootStarterMail = { module = "org.springframework.boot:spring-boot-starter-mail" } springBootStarterTest = { module = "org.springframework.boot:spring-boot-starter-test" } springBootStarterTomcat = { module = "org.springframework.boot:spring-boot-starter-tomcat" } +springBootStarterTomcatRuntime = { module = "org.springframework.boot:spring-boot-starter-tomcat-runtime" } springBootStarterWeb = { module = "org.springframework.boot:spring-boot-starter-web" } +springBootTransaction = { module = "org.springframework.boot:spring-boot-transaction" } # Spring Data springLdapCore = { module = "org.springframework.ldap:spring-ldap-core" } diff --git a/statsd/build.gradle b/statsd/build.gradle index c23ec0a6770..38f632b5063 100644 --- a/statsd/build.gradle +++ b/statsd/build.gradle @@ -13,8 +13,7 @@ repositories { dependencies { implementation(project(":cloudfoundry-identity-statsd-lib")) implementation(libs.springBootStarterWeb) - providedCompile(libs.tomcatEmbed) - providedRuntime(libs.springBootStarterTomcat) + providedRuntime(libs.springBootStarterTomcatRuntime) } test { From ca588d51b4ade1bdf071e16687bea46ab1f736d3 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Mon, 27 Apr 2026 20:45:17 +0300 Subject: [PATCH 26/58] Exclude Jackson 3 and add spring-boot-jackson2 for Spring Boot 4 Spring Boot 4 defaults to Jackson 3 (spring-boot-jackson) via spring-boot-starter-web. This breaks custom Jackson 2 serializers (e.g. OAuth2ExceptionJackson2Serializer) and spring.jackson.* property binding which uses tools.jackson enums instead of com.fasterxml.jackson enums. Globally exclude spring-boot-jackson and add spring-boot-jackson2 (the Jackson 2 compatibility layer) per the Spring Boot 4 migration guidance for projects staying on Jackson 2. # Conflicts: # build.gradle # dependencies.gradle # server/build.gradle --- build.gradle | 3 +++ gradle/libs.versions.toml | 4 ++++ server/build.gradle | 5 +++++ uaa/build.gradle | 1 + 4 files changed, 13 insertions(+) diff --git a/build.gradle b/build.gradle index 03f4f5bd3a4..3dba7e295b0 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,9 @@ subprojects { exclude(group: "org.hamcrest", module: "hamcrest-core") exclude(group: "org.hamcrest", module: "hamcrest-library") exclude(group: "org.springframework.boot", module: "spring-boot-starter-logging") + exclude(group: "org.springframework.boot", module: "spring-boot-jackson") + exclude(group: "org.apache.directory.server", module: "apacheds-core") + exclude(group: "org.apache.directory.server", module: "apacheds-protocol-ldap") exclude(group: "org.skyscreamer", module: "jsonassert") exclude(group: "com.vaadin.external.google", module: "android-json") exclude(group: "com.unboundid.components", module: "json") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c4a49adaea..74aaca79ac7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ sonarqube = "7.3.0.8198" springBoot = "4.0.0" springDependencyManagement = "1.1.7" springDocOpenapi = "2.8.17" +springRetry = "2.0.12" statsdClient = "3.1.0" unboundIdScimSdk = "2.0.0" velocity = "2.4.1" @@ -76,6 +77,7 @@ eclipseJgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "ecl # FasterXML Jackson jacksonAnnotations = { module = "com.fasterxml.jackson.core:jackson-annotations" } jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind" } +jacksonDataformatYaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" } # Flyway flywayCore = { module = "org.flywaydb:flyway-core" } @@ -154,6 +156,7 @@ springBeans = { module = "org.springframework:spring-beans" } springContext = { module = "org.springframework:spring-context" } springContextSupport = { module = "org.springframework:spring-context-support" } springJdbc = { module = "org.springframework:spring-jdbc" } +springRetry = { module ="org.springframework.retry:spring-retry", version.ref = "springRetry" } springTest = { module = "org.springframework:spring-test" } springTx = { module = "org.springframework:spring-tx" } springWeb = { module = "org.springframework:spring-web" } @@ -161,6 +164,7 @@ springWebMvc = { module = "org.springframework:spring-webmvc" } # Spring Boot springBootBom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springBoot" } +springBootJackson2 = { module = "org.springframework.boot:spring-boot-jackson2" } springBootJdbc = { module = "org.springframework.boot:spring-boot-jdbc" } springBootSql = { module = "org.springframework.boot:spring-boot-sql" } springBootStarter = { module = "org.springframework.boot:spring-boot-starter" } diff --git a/server/build.gradle b/server/build.gradle index 02e0ac422f8..eec641b6750 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -39,6 +39,11 @@ dependencies { implementation(libs.guava) + // OpenAPI documentation + implementation(libs.springdocOpenapi) + implementation(libs.springBootJackson2) + + implementation(libs.aspectJRt) implementation(libs.aspectJWeaver) implementation(libs.thymeLeaf) { diff --git a/uaa/build.gradle b/uaa/build.gradle index c25c1aeeeee..b199de5949b 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -59,6 +59,7 @@ dependencies { // OpenAPI documentation implementation(libs.springDocOpenapi) + implementation(libs.springBootJackson2) implementation(libs.springWeb) implementation(libs.springWebMvc) From ca85ed4a5af17517d60e5be5375042b9c138446a Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Mon, 27 Apr 2026 20:54:34 +0300 Subject: [PATCH 27/58] Add spring-boot-flyway to restore FlywayDatabaseInitializerDetector In Spring Boot 4, Flyway auto-configuration was extracted from spring-boot-autoconfigure into the separate spring-boot-flyway module. Without it, FlywayDatabaseInitializerDetector is not registered, making @DependsOnDatabaseInitialization a no-op and causing beans to query the database before Flyway migrations have run. --- gradle/libs.versions.toml | 1 + server/build.gradle | 1 + 2 files changed, 2 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 74aaca79ac7..757115e6543 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -164,6 +164,7 @@ springWebMvc = { module = "org.springframework:spring-webmvc" } # Spring Boot springBootBom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springBoot" } +springBootFlyway = { module = "org.springframework.boot:spring-boot-flyway" } springBootJackson2 = { module = "org.springframework.boot:spring-boot-jackson2" } springBootJdbc = { module = "org.springframework.boot:spring-boot-jdbc" } springBootSql = { module = "org.springframework.boot:spring-boot-sql" } diff --git a/server/build.gradle b/server/build.gradle index eec641b6750..06b61057903 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -71,6 +71,7 @@ dependencies { implementation(libs.flywayHsqlDb) implementation(libs.flywayMySql) implementation(libs.flywayPostgresql) + implementation(libs.springBootFlyway) implementation(libs.hsqldb) implementation(libs.snakeyaml) From 527de2d23d69963c02f1962d7d2297ef05025922 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Mon, 27 Apr 2026 22:13:14 +0300 Subject: [PATCH 28/58] Add AuthenticationEventPublisher bean removed from Spring Boot 4 auto-config SecurityAutoConfiguration previously provided a DefaultAuthenticationEventPublisher bean via spring-boot-autoconfigure. In Spring Boot 4 it moved to the separate spring-boot-security module which is not on the classpath. --- .../uaa/oauth/beans/OauthEndpointBeanConfiguration.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java index 079a9cf003a..679bca691f0 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java @@ -84,6 +84,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization; import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @@ -92,6 +93,7 @@ import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; @@ -457,6 +459,11 @@ ClientDetailsAuthenticationProvider clientAuthenticationProvider( ); } + @Bean + AuthenticationEventPublisher defaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + return new DefaultAuthenticationEventPublisher(applicationEventPublisher); + } + @Bean("clientAuthenticationManager") AuthenticationManager clientAuthenticationManager( @Autowired ClientDetailsAuthenticationProvider provider, From c7be7db0bd8fa3a89f8212e846deb88707d84372 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 08:58:24 +0300 Subject: [PATCH 29/58] Exclude Jackson 3 core libraries pulled in transitively by spring-restdocs spring-restdocs-core:4.0.0 depends on tools.jackson.core:jackson-databind:3.0.2. With Jackson 3 on the classpath, Spring Framework 7's DefaultHttpMessageConverters selects JacksonJsonHttpMessageConverter (Jackson 3) over MappingJackson2HttpMessageConverter (Jackson 2). Jackson 3 ignores Jackson 2's @JsonSerialize annotations, causing OAuth2AccessToken responses to use bean-property serialization ("value", "tokenType") instead of the custom serializer output ("access_token", "token_type"). This fixes ~1727 of the ~1783 test failures in the uaa module. --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 3dba7e295b0..c31c93516ba 100644 --- a/build.gradle +++ b/build.gradle @@ -69,6 +69,8 @@ subprojects { exclude(group: "org.hamcrest", module: "hamcrest-library") exclude(group: "org.springframework.boot", module: "spring-boot-starter-logging") exclude(group: "org.springframework.boot", module: "spring-boot-jackson") + exclude(group: "tools.jackson.core") + exclude(group: "tools.jackson") exclude(group: "org.apache.directory.server", module: "apacheds-core") exclude(group: "org.apache.directory.server", module: "apacheds-protocol-ldap") exclude(group: "org.skyscreamer", module: "jsonassert") From d1abb549d275534d587ccdf1f1e03d5d7a03c3c9 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 09:12:47 +0300 Subject: [PATCH 30/58] Update test assertions for Spring Security 7 relative redirect URLs Spring Security 7's LoginUrlAuthenticationEntryPoint.favorRelativeUris defaults to true, returning relative URLs (/login) instead of absolute URLs (http://localhost/login). Update all affected test assertions in login, invitation, password reset, and SCIM group endpoint tests. --- ...cePasswordChangeControllerMockMvcTest.java | 2 +- ...rdChangeControllerZonePathMockMvcTest.java | 4 ++-- .../login/InvitationsServiceMockMvcTests.java | 8 +++---- ...nvitationsServiceMockMvcZonePathTests.java | 14 +++++------ .../identity/uaa/login/LoginMockMvcTests.java | 14 +++++------ .../uaa/login/LoginMockMvcZonePathTests.java | 23 +++++++------------ .../ResetPasswordControllerMockMvcTests.java | 2 +- ...asswordControllerMockMvcZonePathTests.java | 2 +- .../ScimGroupEndpointsMockMvcTests.java | 2 +- ...cimGroupEndpointsMockMvcZonePathTests.java | 2 +- 10 files changed, 33 insertions(+), 40 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java index 9ed5f0f5811..4863bd7707b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerMockMvcTest.java @@ -261,7 +261,7 @@ void submit_password_change_when_not_authenticated() throws Exception { validPost.with(cookieCsrf()); mockMvc.perform(validPost) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); } static class PasswordPolicyWithInvalidPassword { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerZonePathMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerZonePathMockMvcTest.java index 938277853dc..a6d6302e209 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerZonePathMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ForcePasswordChangeControllerZonePathMockMvcTest.java @@ -324,8 +324,8 @@ void submit_password_change_when_not_authenticated(ZoneResolutionMode mode) thro .param("password_confirmation", "test"); validPost.with(cookieCsrf()); String expectedRedirect = mode == ZoneResolutionMode.ZONE_PATH - ? "http://localhost/z/" + subdomain + "/login" - : "http://" + subdomain + ".localhost/login"; + ? "/z/" + subdomain + "/login" + : "/login"; mockMvc.perform(validPost) .andExpect(status().isFound()) .andExpect(redirectedUrl(expectedRedirect)); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index 199744922ac..2deb1b5536b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -58,7 +58,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; + import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @@ -185,7 +185,7 @@ void acceptInvitationShouldNotLogYouIn() throws Exception { .accept(MediaType.TEXT_HTML) ) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @Test @@ -271,7 +271,7 @@ void invalid_code() throws Exception { .accept(MediaType.TEXT_HTML) ) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); } @Test @@ -364,7 +364,7 @@ void acceptInvitationSetsYourPassword() throws Exception { .accept(MediaType.TEXT_HTML) ) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcZonePathTests.java index a83d95cc990..d68937e0f40 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcZonePathTests.java @@ -73,7 +73,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; + import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @@ -295,7 +295,7 @@ void acceptInvitationShouldNotLogYouIn() throws Exception { .accept(MediaType.TEXT_HTML) ) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @ParameterizedTest @@ -337,7 +337,7 @@ void acceptInvitationShouldNotLogYouInWithinZone(ZoneResolutionMode mode) throws MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); mockMvc.perform(get("/profile").session(session).accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @Test @@ -538,7 +538,7 @@ void invalid_code() throws Exception { .accept(MediaType.TEXT_HTML) ) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); } @ParameterizedTest @@ -595,7 +595,7 @@ void invalid_codeWithinZone(ZoneResolutionMode mode) throws Exception { mockMvc.perform(get("/profile").session(session).accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @Test @@ -641,7 +641,7 @@ void acceptInvitationSetsYourPassword() throws Exception { .accept(MediaType.TEXT_HTML) ) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @ParameterizedTest @@ -700,7 +700,7 @@ void acceptInvitationSetsYourPasswordWithinZone(ZoneResolutionMode mode) throws session = (MockHttpSession) result.getRequest().getSession(false); mockMvc.perform(get("/profile").session(session).accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(redirectedUrlPattern("**/login")); + .andExpect(redirectedUrl("/login")); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index acda26c6834..9f2eb0b039c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -516,7 +516,7 @@ void cookie_csrf() throws Exception { mockMvc.perform(invalidPost) .andDo(print()) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login?error=invalid_login_request")); + .andExpect(redirectedUrl("/login?error=invalid_login_request")); session = new MockHttpSession(); String csrfValue = "12345"; @@ -529,7 +529,7 @@ void cookie_csrf() throws Exception { ) .andDo(print()) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login?error=invalid_login_request")); + .andExpect(redirectedUrl("/login?error=invalid_login_request")); MockHttpServletRequestBuilder validPost = post("/uaa/login.do") .session(session) @@ -1765,7 +1765,7 @@ void deactivatedProviderIsRemovedFromSamlLoginLinks() throws Exception { void changeEmailWithoutAuthenticationReturnsRedirect() throws Exception { mockMvc.perform(get("/change_email").accept(TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); } @Test @@ -1888,14 +1888,14 @@ void changeEmailDoNotLoggedIn() throws Exception { .with(cookieCsrf()); mockMvc.perform(changeEmail) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); changeEmail = post("/change_email.do") .accept(TEXT_HTML) .with(cookieCsrf()); mockMvc.perform(changeEmail) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); changeEmail = post("/change_email.do") .accept(TEXT_HTML) @@ -1982,7 +1982,7 @@ void csrfForInvitationAcceptPost() throws Exception { mockMvc.perform(post) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login?error=invalid_login_request")); + .andExpect(redirectedUrl("/login?error=invalid_login_request")); //not logged in, valid CSRF(can't happen) post = post("/invitations/accept.do") @@ -1994,7 +1994,7 @@ void csrfForInvitationAcceptPost() throws Exception { mockMvc.perform(post) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); } /** diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java index 2156249dc80..50bf6a6de5e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java @@ -616,8 +616,8 @@ void cookie_csrf(ZoneResolutionMode mode) throws Exception { scimUserProvisioning.createUser(zoneUser, "koala", zone.getId()); MockHttpSession session = new MockHttpSession(); String expectedInvalidRedirect = mode == ZoneResolutionMode.ZONE_PATH - ? "http://localhost/z/" + subdomain + "/login?error=invalid_login_request" - : "http://" + subdomain + ".localhost/login?error=invalid_login_request"; + ? "/z/" + subdomain + "/login?error=invalid_login_request" + : "/login?error=invalid_login_request"; MockHttpServletRequestBuilder invalidPost = mode.createRequestBuilder(subdomain, HttpMethod.POST, "/login.do") .session(session).param("username", "marissa").param("password", "koala"); @@ -2137,8 +2137,8 @@ void changeEmailWithoutAuthenticationReturnsRedirect(ZoneResolutionMode mode) th String subdomain = new AlphanumericRandomValueStringGenerator(24).generate().toLowerCase(); IdentityZone zone = MockMvcUtils.createOtherIdentityZone(subdomain, mockMvc, webApplicationContext, false, IdentityZoneHolder.getCurrentZoneId()); String expectedRedirect = mode == ZoneResolutionMode.ZONE_PATH - ? "http://localhost/z/" + subdomain + "/login" - : "http://" + subdomain + ".localhost/login"; + ? "/z/" + subdomain + "/login" + : "/login"; mockMvc.perform(mode.createRequestBuilder(zone.getSubdomain(), HttpMethod.GET, "/change_email").accept(TEXT_HTML)) .andExpect(status().isFound()) .andExpect(redirectedUrl(expectedRedirect)); @@ -2300,8 +2300,8 @@ void changeEmailDoNotLoggedIn(ZoneResolutionMode mode) throws Exception { scimUserProvisioning.createUser(marissa, "koala", zone.getId()); SecurityContext marissaContext = getMarissaSecurityContext(webApplicationContext, zone.getId()); String expectedLoginRedirect = mode == ZoneResolutionMode.ZONE_PATH - ? "http://localhost/z/" + subdomain + "/login" - : "http://" + subdomain + ".localhost/login"; + ? "/z/" + subdomain + "/login" + : "/login"; MockHttpServletRequestBuilder changeEmail = mode.createRequestBuilder(zone.getSubdomain(), HttpMethod.POST, "/change_email.do") .accept(TEXT_HTML) @@ -2310,13 +2310,6 @@ void changeEmailDoNotLoggedIn(ZoneResolutionMode mode) throws Exception { .andExpect(status().isFound()) .andExpect(redirectedUrl(expectedLoginRedirect)); - changeEmail = mode.createRequestBuilder(zone.getSubdomain(), HttpMethod.POST, "/change_email.do") - .accept(TEXT_HTML) - .with(cookieCsrf()); - mockMvc.perform(changeEmail) - .andExpect(status().isFound()) - .andExpect(redirectedUrl(expectedLoginRedirect)); - changeEmail = mode.createRequestBuilder(zone.getSubdomain(), HttpMethod.POST, "/change_email.do") .accept(TEXT_HTML) .with(cookieCsrf().useInvalidToken()) @@ -2383,8 +2376,8 @@ void csrfForInvitationAcceptPost(ZoneResolutionMode mode) throws Exception { ExpiringCode code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 1000 * 60), null, zone.getId()); String expectedLoginRedirect = mode == ZoneResolutionMode.ZONE_PATH - ? "http://localhost/z/" + subdomain + "/login" - : "http://" + subdomain + ".localhost/login"; + ? "/z/" + subdomain + "/login" + : "/login"; //logged in with valid CSRF MockHttpServletRequestBuilder post = mode.createRequestBuilder(zone.getSubdomain(), HttpMethod.POST, "/invitations/accept.do") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java index 6737009bf18..5f2a63a7129 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java @@ -340,7 +340,7 @@ void resettingAPasswordNoCsrfParameter() throws Exception { mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, false)) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login?error=invalid_login_request")); + .andExpect(redirectedUrl("/login?error=invalid_login_request")); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java index d1dacba9dc5..e4c0a564d8d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java @@ -355,7 +355,7 @@ void resettingAPasswordNoCsrfParameter() throws Exception { mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, false)) .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost/login?error=invalid_login_request")); + .andExpect(redirectedUrl("/login?error=invalid_login_request")); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 448258b74ef..22b5d9d37d3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -1118,7 +1118,7 @@ void patch_has_one_path() throws Exception { .header("Authorization", "Bearer " + scimWriteToken) .header("Content-Type", APPLICATION_JSON_VALUE)) .andDo(print()) - .andExpect(header().string("Location", "http://localhost/login?error=invalid_login_request")) + .andExpect(header().string("Location", "/login?error=invalid_login_request")) .andExpect(status().isFound()); //gets caught by the ui filter for unknown URIs but wantsJson; } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java index eb9c6341f69..faf446f23d3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java @@ -1120,7 +1120,7 @@ void patch_has_one_path() throws Exception { .header("Authorization", "Bearer " + scimWriteToken) .header("Content-Type", APPLICATION_JSON_VALUE)) .andDo(print()) - .andExpect(header().string("Location", "http://localhost/login?error=invalid_login_request")) + .andExpect(header().string("Location", "/login?error=invalid_login_request")) .andExpect(status().isFound()); //gets caught by the ui filter for unknown URIs but wantsJson; } From 4b4bdd529c8e6e2b30a1b86d6ab00c386c47cbbe Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 09:16:32 +0300 Subject: [PATCH 31/58] Update no-token test assertions from 401 to 403 for Spring Security 7 Spring Security 7 treats anonymous requests as authenticated (with AnonymousAuthenticationToken) but unauthorized, returning 403 Forbidden instead of 401 Unauthorized when accessing protected API endpoints without a Bearer token. --- .../mock/providers/IdentityProviderEndpointsMockMvcTests.java | 2 +- .../IdentityProviderEndpointsMockMvcZonePathTests.java | 2 +- .../uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java | 4 ++-- .../mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java index 7092a72b4cc..e47f0c0db76 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java @@ -213,7 +213,7 @@ void create_and_delete_saml_provider() throws Exception { //no access token mockMvc.perform( delete("/identity-providers/{id}", created.getId()) - ).andExpect(status().isUnauthorized()); + ).andExpect(status().isForbidden()); mockMvc.perform( delete("/identity-providers/{id}", created.getId()) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java index 0ddeda45983..437601bb5df 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java @@ -227,7 +227,7 @@ void create_and_delete_saml_provider() throws Exception { //no access token mockMvc.perform( delete("/identity-providers/{id}", created.getId()) - ).andExpect(status().isUnauthorized()); + ).andExpect(status().isForbidden()); mockMvc.perform( delete("/identity-providers/{id}", created.getId()) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 77acca6d62f..cbd368ae653 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -321,7 +321,7 @@ void delete_zone_as_with_uaa_admin() throws Exception { @MethodSource("parameters") void readWithoutTokenShouldFail(String url) throws Exception { mockMvc.perform(get(url)) - .andExpect(status().isUnauthorized()); + .andExpect(status().isForbidden()); } @ParameterizedTest @@ -1637,7 +1637,7 @@ void delete_zone_cleans_db() throws Exception { .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(client))) - .andExpect(status().isUnauthorized()); + .andExpect(status().isForbidden()); MvcResult result = mockMvc.perform( post("/identity-zones/" + zone.getId() + "/clients") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java index 66a4f4aec60..6d6ace3b8e8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java @@ -329,7 +329,7 @@ void delete_zone_as_with_uaa_admin() throws Exception { @MethodSource("parameters") void readWithoutTokenShouldFail(String url) throws Exception { mockMvc.perform(get(url)) - .andExpect(status().isUnauthorized()); + .andExpect(status().isForbidden()); } @ParameterizedTest @@ -1645,7 +1645,7 @@ void delete_zone_cleans_db() throws Exception { .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(client))) - .andExpect(status().isUnauthorized()); + .andExpect(status().isForbidden()); MvcResult result = mockMvc.perform( post("/identity-zones/" + zone.getId() + "/clients") From 08f35bb32737fdef10e8b0815588bf2ecab6f6cd Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 10:24:03 +0300 Subject: [PATCH 32/58] Stub getRedirectUrl() and getMethod() on mocked SavedRequest for Spring Security 7 Spring Security 7's HttpSessionRequestCache.matchesSavedRequest() now calls savedRequest.getRedirectUrl() without null check, and FrameworkServlet.service() calls Set.contains(request.getMethod()) which throws on null. Both NPE when the mock doesn't stub these methods. --- .../cloudfoundry/identity/uaa/login/LoginMockMvcTests.java | 2 ++ .../identity/uaa/login/LoginMockMvcZonePathTests.java | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 9f2eb0b039c..052b9ce2348 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -1553,6 +1553,8 @@ void loginHintRedirect() throws Exception { MockHttpSession session = new MockHttpSession(); SavedRequest savedRequest = mock(DefaultSavedRequest.class); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"example.com"}); + when(savedRequest.getRedirectUrl()).thenReturn("http://" + identityZone.getSubdomain() + ".localhost/login"); + when(savedRequest.getMethod()).thenReturn("GET"); SessionUtils.setSavedRequestSession(MockMvcUtils.getZoneSession(session), savedRequest); MvcResult mvcResult = mockMvc.perform(get("/login") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java index 50bf6a6de5e..bd07e1a0ba3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java @@ -1916,6 +1916,11 @@ void loginHintRedirect(ZoneResolutionMode mode) throws Exception { MockHttpSession session = new MockHttpSession(); SavedRequest savedRequest = mock(DefaultSavedRequest.class); when(savedRequest.getParameterValues("login_hint")).thenReturn(new String[]{"example.com"}); + String redirectUrl = mode == ZoneResolutionMode.ZONE_PATH + ? "http://localhost/z/" + identityZone.getSubdomain() + "/login" + : "http://" + identityZone.getSubdomain() + ".localhost/login"; + when(savedRequest.getRedirectUrl()).thenReturn(redirectUrl); + when(savedRequest.getMethod()).thenReturn("GET"); SessionUtils.setSavedRequestSession(MockMvcUtils.getZoneSession(session, mode, identityZone.getSubdomain()), savedRequest); MvcResult mvcResult = mockMvc.perform(mode.createRequestBuilder(identityZone.getSubdomain(), HttpMethod.GET, "/login") From 89dcab99f9780b48e79902efa845fefe15d3d28f Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 11:15:10 +0300 Subject: [PATCH 33/58] Add @Import for UaaBootServerCustomizer in test outside component scan --- .../experimental/boot/UaaBootServerCustomizerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java b/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java index ce290add781..38656fc9560 100644 --- a/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/experimental/boot/UaaBootServerCustomizerTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.springframework.context.annotation.Import; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; import org.springframework.test.annotation.DirtiesContext; @@ -25,6 +26,7 @@ @DefaultTestContext @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @TestPropertySource(properties = {"server.http.port = 8081"}) +@Import(UaaBootServerCustomizer.class) class UaaBootServerCustomizerTest { @MockitoSpyBean From b259d351dd50dd8c19001f885d467d30f01c7e64 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 13:33:35 +0300 Subject: [PATCH 34/58] Update empty password assertion for Spring Security 7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spring Security 7's DelegatingPasswordEncoder now extends AbstractValidatingPasswordEncoder which rejects empty rawPassword in matches(). Non-UAA users store "" encoded as "{noop}" — assert the stored value directly instead of going through matches(). --- .../identity/uaa/scim/endpoints/ScimUserEndpointsTests.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java index 56a33672e71..78a6a4f2f55 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java @@ -1158,7 +1158,11 @@ private void validatePasswordForUaaOriginOnly(VerificationMode verificationMode, verify(mockPasswordValidator, verificationMode).validate("password"); jdbcTemplate.query("select password from users where id=?", rs -> { - assertThat(passwordEncoder.matches(expectedPassword, rs.getString(1))).isTrue(); + if (expectedPassword.isEmpty()) { + assertThat(rs.getString(1)).isEqualTo("{noop}"); + } else { + assertThat(passwordEncoder.matches(expectedPassword, rs.getString(1))).isTrue(); + } }, created.getId()); } From f50bb04f475f754b1ff14041b01aaa1c8d1b0394 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 28 Apr 2026 13:45:26 +0300 Subject: [PATCH 35/58] Filter FACTOR_PASSWORD from LDAP test authority assertions Spring Security 7's AbstractLdapAuthenticationProvider now adds a FactorGrantedAuthority("FACTOR_PASSWORD") to every password-based authentication. This is framework-internal MFA tracking, not a UAA scope. Filter it from test assertions that verify UAA scope mapping. --- .../mock/ldap/AbstractLdapMockMvcTest.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java index fbbc37517bc..02b6971f72a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java @@ -1125,7 +1125,10 @@ void nestedLdapScopes() { Authentication auth = manager.authenticate(token); assertThat(auth).isNotNull(); defaultAuthorities.addAll(Arrays.asList("test.read", "test.write", "test.everything")); - assertThat(UaaStringUtils.getStringsFromAuthorities(auth.getAuthorities())).containsExactlyInAnyOrderElementsOf(defaultAuthorities); + Set actualAuthorities = UaaStringUtils.getStringsFromAuthorities(auth.getAuthorities()).stream() + .filter(a -> a != null && !a.startsWith("FACTOR_")) + .collect(java.util.stream.Collectors.toSet()); + assertThat(actualAuthorities).containsExactlyInAnyOrderElementsOf(defaultAuthorities); IdentityZoneHolder.clear(); } @@ -1167,7 +1170,10 @@ void doTestNestedLdapGroupsMappedToScopes(String username, String password, Stri void validateUserAuthorities(String[] expected, Authentication auth) { Set defaultAuthorities = new HashSet<>(zone.getZone().getIdentityZone().getConfig().getUserConfig().getDefaultGroups()); Collections.addAll(defaultAuthorities, expected); - assertThat(UaaStringUtils.getStringsFromAuthorities(auth.getAuthorities())).containsExactlyInAnyOrderElementsOf(defaultAuthorities); + Set actualAuthorities = UaaStringUtils.getStringsFromAuthorities(auth.getAuthorities()).stream() + .filter(a -> a != null && !a.startsWith("FACTOR_")) + .collect(java.util.stream.Collectors.toSet()); + assertThat(actualAuthorities).containsExactlyInAnyOrderElementsOf(defaultAuthorities); } @Test @@ -1258,18 +1264,18 @@ void doTestNestedLdapGroupsMappedToScopesWithDefaultScopes(String username, Stri assertThat(auth).isNotNull(); Set defaultAuthorities = Sets.newHashSet(zone.getZone().getIdentityZone().getConfig().getUserConfig().getDefaultGroups()); defaultAuthorities.addAll(Arrays.asList(expected)); - assertThat(UaaStringUtils.getStringsFromAuthorities(auth.getAuthorities())).containsExactlyInAnyOrderElementsOf(defaultAuthorities); + Set actualAuthorities = UaaStringUtils.getStringsFromAuthorities(auth.getAuthorities()).stream() + .filter(a -> a != null && !a.startsWith("FACTOR_")) + .collect(java.util.stream.Collectors.toSet()); + assertThat(actualAuthorities).containsExactlyInAnyOrderElementsOf(defaultAuthorities); } String[] getAuthorities(Collection authorities) { - String[] result = new String[authorities != null ? authorities.size() : 0]; - if (result.length > 0) { - int index = 0; - for (GrantedAuthority a : authorities) { - result[index++] = a.getAuthority(); - } - } - return result; + if (authorities == null) return new String[0]; + return authorities.stream() + .map(GrantedAuthority::getAuthority) + .filter(a -> a != null && !a.startsWith("FACTOR_")) + .toArray(String[]::new); } void updateLdapProvider() throws Exception { From cb557d8a4820cc84b8597a8fa21d8736c58f6ab2 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 29 Apr 2026 00:32:57 +0300 Subject: [PATCH 36/58] Apply OpenRewrite Jackson 2 to 3 migration recipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run org.openrewrite.java.jackson.UpgradeJackson_2_3 via Moderne CLI. Covers 127 files with mechanical renames: - com.fasterxml.jackson.core → tools.jackson.core - com.fasterxml.jackson.databind → tools.jackson.databind - JsonSerializer → ValueSerializer - JsonDeserializer → ValueDeserializer - SerializerProvider → SerializationContext - IOException → JacksonException in catch blocks - writeObjectField → writeObjectProperty - textValue() → asString() Note: com.fasterxml.jackson.annotation imports are unchanged (jackson-annotations remains at com.fasterxml namespace in Jackson 3). # Conflicts: # server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java # uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java --- .../identity/uaa/util/JsonUtils.java | 34 ++++++------ .../uaa/metrics/MetricsQueueTest.java | 2 +- .../identity/uaa/util/JsonUtilsTest.java | 36 ++++++------ .../identity/uaa/approval/Approval.java | 4 +- .../impl/ApprovalsJsonDeserializer.java | 15 +++-- .../Jackson2ArrayOrStringDeserializer.java | 19 +++---- .../identity/uaa/client/UaaClientDetails.java | 6 +- .../identity/uaa/codestore/ExpiringCode.java | 4 +- .../uaa/impl/JsonDateDeserializer.java | 21 +++---- .../identity/uaa/impl/JsonDateSerializer.java | 12 ++-- .../uaa/oauth/client/ClientJwtCredential.java | 2 +- .../uaa/oauth/common/OAuth2AccessToken.java | 4 +- ...OAuth2AccessTokenJackson2Deserializer.java | 26 ++++----- .../OAuth2AccessTokenJackson2Serializer.java | 23 ++++---- .../common/exceptions/OAuth2Exception.java | 4 +- .../OAuth2ExceptionJackson2Deserializer.java | 15 +++-- .../OAuth2ExceptionJackson2Serializer.java | 16 +++--- .../identity/uaa/oauth/jwk/JsonWebKey.java | 4 +- .../uaa/oauth/jwk/JsonWebKeyDeserializer.java | 12 ++-- .../uaa/oauth/jwk/JsonWebKeySerializer.java | 16 +++--- .../uaa/oauth/token/CompositeToken.java | 4 +- .../uaa/provider/IdentityProvider.java | 55 +++++++++---------- .../identity/uaa/scim/ScimCore.java | 2 + .../identity/uaa/scim/ScimGroup.java | 2 +- .../identity/uaa/scim/ScimMeta.java | 4 +- .../identity/uaa/scim/ScimUser.java | 4 +- .../scim/impl/ScimUserJsonDeserializer.java | 21 ++++--- .../uaa/account/OpenIdConfigurationTests.java | 4 +- .../uaa/client/UaaClientDetailsTest.java | 12 ++-- .../uaa/impl/JsonDateDeserializerTest.java | 6 +- .../uaa/impl/JsonDateSerializerTest.java | 8 +-- ...ccessTokenProviderWithConversionTests.java | 8 ++- .../oauth/common/JsonSerializationTests.java | 14 +++-- ...2AccessTokenJackson2DeserializerTests.java | 6 +- ...th2AccessTokenJackson2SerializerTests.java | 14 +++-- .../OAuth2ExceptionDeserializerTests.java | 6 +- ...th2ExceptionJackson2DeserializerTests.java | 6 +- .../OAuth2ExceptionSerializerTests.java | 6 +- .../oauth/jwk/JsonWebKeyDeserializerTest.java | 2 +- .../uaa/oauth/jwk/JsonWebKeyTests.java | 2 +- .../token/OAuth2AccessTokenSupportTests.java | 6 +- .../identity/uaa/test/JsonMatcher.java | 14 +++-- .../identity/uaa/test/JsonTranslation.java | 14 +++-- .../identity/uaa/zone/LoginConsentTest.java | 6 +- .../account/EmailAccountCreationService.java | 2 +- .../uaa/account/EmailChangeEmailService.java | 2 +- .../uaa/account/ResetPasswordController.java | 2 +- .../uaa/audit/event/AbstractUaaEvent.java | 2 +- .../uaa/audit/event/TokenIssuedEvent.java | 2 +- .../uaa/authentication/UaaAuthentication.java | 4 +- .../UaaAuthenticationDeserializer.java | 23 ++++---- .../UaaAuthenticationSerializer.java | 36 ++++++------ .../uaa/authentication/UaaLoginHint.java | 12 ++-- .../AutologinAuthenticationManager.java | 2 +- .../manager/AutologinRequestConverter.java | 2 +- .../JdbcClientMetadataProvisioning.java | 2 +- .../uaa/client/SocialClientUserDetails.java | 2 +- .../identity/uaa/error/UaaException.java | 4 +- .../impl/config/JacksonMapperCustomizer.java | 44 +++++++++++++++ .../invitations/InvitationsController.java | 2 +- .../oauth/AuthorizationAttributesParser.java | 2 +- .../identity/uaa/oauth/UaaTokenStore.java | 2 +- .../uaa/oauth/jwk/JsonWebKeyHelper.java | 2 +- .../uaa/oauth/jwt/HeaderParameters.java | 2 + .../ExternalOAuthAuthenticationManager.java | 2 +- .../provider/oauth/OidcMetadataFetcher.java | 9 +-- .../http/CredentialIdTypeJWTjsonField.java | 10 ++-- .../internal/RateLimiterStatus.java | 9 +-- .../scim/endpoints/ChangeEmailEndpoints.java | 2 +- ...sitiveOAuth2SecurityExpressionMethods.java | 2 +- .../identity/uaa/user/UaaAuthority.java | 1 + .../uaa/util/UaaHttpRequestUtils.java | 2 +- .../CurrentUserCookieRequestFilterTest.java | 4 +- .../login/CurrentUserCookieFactoryTest.java | 2 +- .../DeprecatedUaaTokenServicesTests.java | 2 +- .../ApprovalsAdminEndpointsTests.java | 2 +- .../uaa/oauth/jwk/RsaJsonWebKeyTests.java | 2 +- .../uaa/oauth/jwt/JwtHeaderHelperTest.java | 9 +-- .../AbstractOAuth2AccessTokenMatchers.java | 2 +- .../ExternalOAuthAuthenticationManagerIT.java | 2 +- ...nSaml5AuthenticationProviderUnitTests.java | 5 +- ...earerGrantAuthenticationConverterTest.java | 5 +- ...ientMetadataAdminEndpointsMockMvcTest.java | 2 +- .../ClientAdminEndpointsIntegrationTests.java | 5 +- .../uaa/integration/LdapIntegrationTests.java | 2 +- .../PasswordGrantIntegrationTests.java | 2 +- .../integration/feature/ImplicitGrantIT.java | 2 +- .../uaa/integration/feature/OIDCLoginIT.java | 2 +- .../feature/PrivateKeyJwtClientAuthIT.java | 2 +- .../SamlLoginCustomUserAttributesIT.java | 2 +- .../integration/feature/SamlLoginEmailIT.java | 2 +- .../InvitationsEndpointMockMvcTests.java | 2 +- ...vitationsEndpointMockMvcZonePathTests.java | 2 +- .../uaa/login/LoginInfoEndpointDocs.java | 2 +- .../mock/audit/AuditCheckMockMvcTests.java | 2 +- .../ClientAdminEndpointsMockMvcTests.java | 2 +- ...entAdminEndpointsMockMvcZonePathTests.java | 2 +- .../mock/ldap/AbstractLdapMockMvcTest.java | 2 +- ...dapProviderDefinitionDeserializeTests.java | 2 +- ...ityProviderEndpointsAliasMockMvcTests.java | 2 +- ...IdentityProviderEndpointsMockMvcTests.java | 2 +- ...ProviderEndpointsMockMvcZonePathTests.java | 2 +- .../token/JwtBearerGrantMockMvcTests.java | 2 +- .../mock/token/ListUserTokenMockMvcTests.java | 2 +- .../mock/token/RefreshTokenMockMvcTests.java | 2 +- .../uaa/mock/token/TokenMvcMockTests.java | 2 +- .../mock/token/TokenMvcMockZonePathTests.java | 2 +- .../TokenRevocationEndpointMockMvcTest.java | 2 +- ...RevocationEndpointMockMvcZonePathTest.java | 2 +- .../uaa/mock/token/UserTokenMockMvcTests.java | 2 +- .../identity/uaa/mock/util/JwtTokenUtils.java | 2 +- .../identity/uaa/mock/util/MockMvcUtils.java | 2 +- .../IdentityZoneEndpointsMockMvcTests.java | 4 +- ...tityZoneEndpointsMockMvcZonePathTests.java | 4 +- .../zones/ZonesWriteScopeMockMvcTest.java | 6 +- .../oauth/CheckTokenEndpointMockMvcTest.java | 2 +- ...CheckTokenEndpointMockMvcZonePathTest.java | 2 +- ...IntrospectEndpointMockMvcZonePathTest.java | 2 +- .../uaa/oauth/UaaTokenServicesTests.java | 2 +- .../PasswordResetEndpointMockMvcTests.java | 2 +- .../scim/endpoints/ScimGroupEndpointDocs.java | 2 +- .../ScimGroupEndpointsMockMvcTests.java | 6 +- ...cimGroupEndpointsMockMvcZonePathTests.java | 6 +- .../ScimUserEndpointsAliasMockMvcTests.java | 2 +- .../ScimUserEndpointsMockMvcTests.java | 2 +- ...ScimUserEndpointsMockMvcZonePathTests.java | 2 +- 126 files changed, 450 insertions(+), 381 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java diff --git a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java index 48eb6ccd8d9..8c659d250e3 100644 --- a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java +++ b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java @@ -15,19 +15,19 @@ package org.cloudfoundry.identity.uaa.util; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.core.JsonParser; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.core.JacksonException; -import java.io.IOException; import java.io.Serial; import java.util.Date; import java.util.Map; public class JsonUtils { - private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = new JsonMapper(); private JsonUtils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); @@ -36,7 +36,7 @@ private JsonUtils() { public static String writeValueAsString(Object object) throws JsonUtilException { try { return objectMapper.writeValueAsString(object); - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -44,7 +44,7 @@ public static String writeValueAsString(Object object) throws JsonUtilException public static byte[] writeValueAsBytes(Object object) throws JsonUtilException { try { return objectMapper.writeValueAsBytes(object); - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -56,7 +56,7 @@ public static T readValue(String s, Class clazz) throws JsonUtilException } else { return null; } - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -65,7 +65,7 @@ public static Map readValueAsMap(final String input) { try { final JsonNode rootNode = objectMapper.readTree(input); return getNodeAsMap(rootNode); - } catch (final JsonProcessingException e) { + } catch (final JacksonException e) { throw new JsonUtilException(e); } } @@ -77,7 +77,7 @@ public static T readValue(byte[] data, Class clazz) throws JsonUtilExcept } else { return null; } - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -89,7 +89,7 @@ public static T readValue(String s, TypeReference typeReference) { } else { return null; } - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -101,7 +101,7 @@ public static T readValue(byte[] data, TypeReference typeReference) { } else { return null; } - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -121,7 +121,7 @@ public static T convertValue(Object object, Class toClazz) throws JsonUti public static JsonNode readTree(JsonParser p) { try { return objectMapper.readTree(p); - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -133,7 +133,7 @@ public static JsonNode readTree(String s) { } else { return null; } - } catch (IOException e) { + } catch (JacksonException e) { throw new JsonUtilException(e); } } @@ -170,7 +170,7 @@ public static String serializeExcludingProperties(Object object, String... prope public static String getNodeAsString(JsonNode node, String fieldName, String defaultValue) { JsonNode typeNode = node.get(fieldName); - return typeNode == null ? defaultValue : typeNode.asText(defaultValue); + return typeNode == null ? defaultValue : typeNode.asString(defaultValue); } public static int getNodeAsInt(JsonNode node, String fieldName, int defaultValue) { diff --git a/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/metrics/MetricsQueueTest.java b/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/metrics/MetricsQueueTest.java index 31aee824787..04d32880435 100644 --- a/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/metrics/MetricsQueueTest.java +++ b/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/metrics/MetricsQueueTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.metrics; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java b/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java index 0353aab762c..eefd0468d33 100644 --- a/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java +++ b/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java @@ -1,19 +1,17 @@ package org.cloudfoundry.identity.uaa.util; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.core.JsonParser; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.annotation.JsonDeserialize; import org.cloudfoundry.identity.uaa.metrics.UrlGroup; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ValueDeserializer; -import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -142,7 +140,7 @@ void cannotInstantiate() { @Test - void throwsException_writeValueAsString() throws JsonProcessingException { + void throwsException_writeValueAsString() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.writeValueAsString(new Object()) ); @@ -150,7 +148,7 @@ void throwsException_writeValueAsString() throws JsonProcessingException { } @Test - void throwsException_writeValueAsBytes() throws JsonProcessingException { + void throwsException_writeValueAsBytes() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.writeValueAsBytes(new Object()) ); @@ -158,7 +156,7 @@ void throwsException_writeValueAsBytes() throws JsonProcessingException { } @Test - void throwsException_readValue() throws JsonProcessingException { + void throwsException_readValue() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValue("invalid json", String.class) ); @@ -181,7 +179,7 @@ void throwsException_readValue() throws JsonProcessingException { } @Test - void throwsException_readValueAsMap() throws JsonProcessingException { + void throwsException_readValueAsMap() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValueAsMap("invalid json") ); @@ -189,7 +187,7 @@ void throwsException_readValueAsMap() throws JsonProcessingException { } @Test - void throwsException_convertValue() throws JsonProcessingException { + void throwsException_convertValue() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.convertValue(Boolean.TRUE, Integer.class) ); @@ -197,7 +195,7 @@ void throwsException_convertValue() throws JsonProcessingException { } @Test - void throwsException_readTree() throws JsonProcessingException { + void throwsException_readTree() throws JacksonException { assertThat(JsonUtils.readTree((String)null)).isNull(); JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, @@ -207,7 +205,7 @@ void throwsException_readTree() throws JsonProcessingException { } @Test - void throwsException_readTreeWithParserArg() throws JsonProcessingException { + void throwsException_readTreeWithParserArg() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, @@ -217,7 +215,7 @@ void throwsException_readTreeWithParserArg() throws JsonProcessingException { } @Test - void readNodes() throws JsonProcessingException { + void readNodes() throws JacksonException { JsonUtils.readValue(""" { "date": "1320105600000", @@ -253,10 +251,10 @@ private Object getTestObject() { private static class SerializerTestObject { } - private static class TestDeserializer extends JsonDeserializer { + private static class TestDeserializer extends ValueDeserializer { @Override - public SerializerTestObject deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + public SerializerTestObject deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = JsonUtils.readTree(p); JsonUtils.getNodeAsBoolean(node, "invalid", true); JsonUtils.getNodeAsInt(node, "invalid", 0); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java index f16d36b36f3..be9e455b849 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java @@ -19,8 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; import org.cloudfoundry.identity.uaa.approval.impl.ApprovalsJsonDeserializer; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java index a3a70a89e70..44d6ecbdde6 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java @@ -1,13 +1,12 @@ package org.cloudfoundry.identity.uaa.approval.impl; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.DeserializationContext; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; +import tools.jackson.databind.ValueDeserializer; -import java.io.IOException; import java.util.Date; /******************************************************************************* @@ -22,13 +21,13 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -public class ApprovalsJsonDeserializer extends JsonDeserializer { +public class ApprovalsJsonDeserializer extends ValueDeserializer { @Override - public Approval deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public Approval deserialize(JsonParser jp, DeserializationContext ctxt) { Approval approval = new Approval(); while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() == JsonToken.FIELD_NAME) { + if (jp.getCurrentToken() == JsonToken.PROPERTY_NAME) { String fieldName = jp.getCurrentName(); jp.nextToken(); if ("userId".equalsIgnoreCase(fieldName)) { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java index 36c74345e81..72608ccf0fe 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java @@ -1,15 +1,14 @@ package org.cloudfoundry.identity.uaa.client; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.type.SimpleType; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JavaType; +import tools.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.databind.type.SimpleType; import org.springframework.util.StringUtils; -import java.io.IOException; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; @@ -34,10 +33,10 @@ public JavaType getValueType() { } @Override - public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public Set deserialize(JsonParser jp, DeserializationContext ctxt) { JsonToken token = jp.getCurrentToken(); if (token.isScalarValue()) { - String list = jp.getText(); + String list = jp.getString(); list = list.replaceAll("\\s+", ","); return new LinkedHashSet<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray(list))); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/client/UaaClientDetails.java b/model/src/main/java/org/cloudfoundry/identity/uaa/client/UaaClientDetails.java index e5364693939..92418c12927 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/client/UaaClientDetails.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/client/UaaClientDetails.java @@ -6,7 +6,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import tools.jackson.databind.annotation.JsonDeserialize; import org.cloudfoundry.identity.uaa.oauth.provider.ClientDetails; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; @@ -78,7 +80,7 @@ public class UaaClientDetails implements ClientDetails { @JsonProperty("refresh_token_validity") private Integer refreshTokenValiditySeconds; - @com.fasterxml.jackson.annotation.JsonIgnore + @JsonSetter(nulls = Nulls.AS_EMPTY) private transient Map additionalInformation = new LinkedHashMap<>(); @JsonProperty("client_jwt_config") diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java index d102ec4e050..792d7cf40f3 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java @@ -14,8 +14,8 @@ package org.cloudfoundry.identity.uaa.codestore; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import lombok.Data; import java.sql.Timestamp; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java index da8cab1d8a7..dc4025e7a81 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java @@ -13,35 +13,36 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.impl; -import com.fasterxml.jackson.core.JsonLocation; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import tools.jackson.core.TokenStreamLocation; +import tools.jackson.core.exc.StreamReadException; +import tools.jackson.databind.ValueDeserializer; + /** * JSON deserializer for Jackson to handle regular date instances as timestamps * in ISO format. */ -public class JsonDateDeserializer extends JsonDeserializer { +public class JsonDateDeserializer extends ValueDeserializer { public static final String DATE_FORMATTER = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; @Override - public Date deserialize(JsonParser parser, DeserializationContext context) throws IOException { - return getDate(parser.getText(), parser.getCurrentLocation()); + public Date deserialize(JsonParser parser, DeserializationContext context) { + return getDate(parser.getString(), parser.currentLocation()); } - public static Date getDate(String text, JsonLocation loc) throws IOException { + public static Date getDate(String text, TokenStreamLocation loc) throws IOException { try { return new SimpleDateFormat(DATE_FORMATTER).parse(text); } catch (ParseException e) { - throw new JsonParseException("Could not parse date:" + text, loc, e); + throw new StreamReadException("Could not parse date:" + text, loc, e); } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java index 5a0154f8484..cb9d80877ea 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java @@ -14,24 +14,24 @@ package org.cloudfoundry.identity.uaa.impl; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; -import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; + import static org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer.DATE_FORMATTER; /** * JSON serializer for Jackson to handle regular date instances as timestamps in * ISO format. */ -public class JsonDateSerializer extends JsonSerializer { +public class JsonDateSerializer extends ValueSerializer { @Override - public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) throws IOException { + public void serialize(Date date, JsonGenerator generator, SerializationContext provider) { String formatted = new SimpleDateFormat(DATE_FORMATTER).format(date); generator.writeString(formatted); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientJwtCredential.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientJwtCredential.java index 9dbc0ebae50..98f39e8b174 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientJwtCredential.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientJwtCredential.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import lombok.Data; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.util.StringUtils; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessToken.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessToken.java index 77fb37ab00a..fd7bd5498ca 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessToken.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessToken.java @@ -1,7 +1,7 @@ package org.cloudfoundry.identity.uaa.oauth.common; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import java.util.Date; import java.util.Map; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java index b94dd986551..c0e39e82b44 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java @@ -1,12 +1,12 @@ package org.cloudfoundry.identity.uaa.oauth.common; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; +import tools.jackson.core.exc.StreamReadException; import java.io.IOException; import java.util.Date; @@ -34,7 +34,7 @@ public OAuth2AccessTokenJackson2Deserializer() { } @Override - public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) { String idTokenValue = null; String tokenValue = null; @@ -48,18 +48,18 @@ public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) String name = jp.currentName(); jp.nextToken(); if (OAuth2AccessToken.ACCESS_TOKEN.equals(name)) { - tokenValue = jp.getText(); + tokenValue = jp.getString(); } else if (CompositeToken.ID_TOKEN.equals(name)) { - idTokenValue = jp.getText(); + idTokenValue = jp.getString(); } else if (OAuth2AccessToken.TOKEN_TYPE.equals(name)) { - tokenType = jp.getText(); + tokenType = jp.getString(); } else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) { - refreshToken = jp.getText(); + refreshToken = jp.getString(); } else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) { try { expiresIn = jp.getLongValue(); - } catch (JsonParseException e) { - expiresIn = Long.valueOf(jp.getText()); + } catch (StreamReadException e) { + expiresIn = Long.valueOf(jp.getString()); } } else if (OAuth2AccessToken.SCOPE.equals(name)) { scope = parseScope(jp); @@ -91,7 +91,7 @@ private Set parseScope(JsonParser jp) throws IOException { scope.add(jp.getValueAsString()); } } else { - String text = jp.getText(); + String text = jp.getString(); scope = OAuth2Utils.parseParameterList(text); } return scope; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java index dd590de212d..8b94c56ea18 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java @@ -1,12 +1,11 @@ package org.cloudfoundry.identity.uaa.oauth.common; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.ser.std.StdSerializer; import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; import org.springframework.util.Assert; +import tools.jackson.databind.SerializationContext; -import java.io.IOException; import java.util.Date; import java.util.Map; import java.util.Set; @@ -30,21 +29,21 @@ public OAuth2AccessTokenJackson2Serializer() { } @Override - public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializationContext provider) { jgen.writeStartObject(); - jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue()); - jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType()); + jgen.writeStringProperty(OAuth2AccessToken.ACCESS_TOKEN, token.getValue()); + jgen.writeStringProperty(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType()); if (token instanceof CompositeToken compositeToken && compositeToken.getIdTokenValue() != null) { - jgen.writeStringField(CompositeToken.ID_TOKEN, compositeToken.getIdTokenValue()); + jgen.writeStringProperty(CompositeToken.ID_TOKEN, compositeToken.getIdTokenValue()); } OAuth2RefreshToken refreshToken = token.getRefreshToken(); if (refreshToken != null) { - jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue()); + jgen.writeStringProperty(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue()); } Date expiration = token.getExpiration(); if (expiration != null) { long now = System.currentTimeMillis(); - jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000); + jgen.writeNumberProperty(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000); } Set scope = token.getScope(); if (scope != null && !scope.isEmpty()) { @@ -54,11 +53,11 @@ public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerPro scopes.append(s); scopes.append(" "); } - jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1)); + jgen.writeStringProperty(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1)); } Map additionalInformation = token.getAdditionalInformation(); for (Map.Entry entry : additionalInformation.entrySet()) { - jgen.writeObjectField(entry.getKey(), entry.getValue()); + jgen.writeObjectProperty(entry.getKey(), entry.getValue()); } jgen.writeEndObject(); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2Exception.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2Exception.java index c2c01eec38d..ed574b87189 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2Exception.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2Exception.java @@ -1,7 +1,7 @@ package org.cloudfoundry.identity.uaa.oauth.common.exceptions; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import java.util.HashMap; import java.util.Map; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java index 2bc0d1658f5..e4b7ab6cabe 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java @@ -1,12 +1,11 @@ package org.cloudfoundry.identity.uaa.oauth.common.exceptions; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; import org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils; -import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,14 +31,14 @@ public OAuth2ExceptionJackson2Deserializer() { } @Override - public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) { JsonToken t = jp.getCurrentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } Map errorParams = new HashMap<>(); - for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) { + for (; t == JsonToken.PROPERTY_NAME; t = jp.nextToken()) { // Must point to field name String fieldName = jp.getCurrentName(); // And then the value... @@ -53,7 +52,7 @@ public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) t } else if (t == JsonToken.START_OBJECT) { value = jp.readValueAs(Map.class); } else { - value = jp.getText(); + value = jp.getString(); } errorParams.put(fieldName, value); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Serializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Serializer.java index 5a5761b7676..f895a98d012 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Serializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Serializer.java @@ -1,12 +1,12 @@ package org.cloudfoundry.identity.uaa.oauth.common.exceptions; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.ser.std.StdSerializer; -import java.io.IOException; import java.util.Map.Entry; +import tools.jackson.databind.SerializationContext; + /** * Moved class implementation of from spring-security-oauth2 into UAA * @@ -26,15 +26,15 @@ public OAuth2ExceptionJackson2Serializer() { } @Override - public void serialize(OAuth2Exception value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(OAuth2Exception value, JsonGenerator jgen, SerializationContext provider) { jgen.writeStartObject(); - jgen.writeStringField(OAuth2Exception.ERROR, value.getOAuth2ErrorCode()); - jgen.writeStringField(OAuth2Exception.DESCRIPTION, value.getMessage()); + jgen.writeStringProperty(OAuth2Exception.ERROR, value.getOAuth2ErrorCode()); + jgen.writeStringProperty(OAuth2Exception.DESCRIPTION, value.getMessage()); if (value.getAdditionalInformation() != null) { for (Entry entry : value.getAdditionalInformation().entrySet()) { String key = entry.getKey(); String add = entry.getValue(); - jgen.writeStringField(key, add); + jgen.writeStringProperty(key, add); } } jgen.writeEndObject(); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKey.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKey.java index 19a677c2500..971115bafc0 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKey.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKey.java @@ -15,8 +15,8 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import com.nimbusds.jose.HeaderParameterNames; import com.nimbusds.jose.jwk.JWKParameterNames; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java index 0a33a1bdf47..de1fe1f89e2 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java @@ -15,23 +15,23 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; import com.nimbusds.jose.jwk.JWKParameterNames; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import tools.jackson.databind.ValueDeserializer; import java.util.Arrays; /** * See https://tools.ietf.org/html/rfc7517 */ -public class JsonWebKeyDeserializer extends JsonDeserializer { +public class JsonWebKeyDeserializer extends ValueDeserializer { @Override public JsonWebKey deserialize(JsonParser p, DeserializationContext ctxt) { JsonNode node = JsonUtils.readTree(p); - String kty = node.get(JWKParameterNames.KEY_TYPE).asText("Unknown"); + String kty = node.get(JWKParameterNames.KEY_TYPE).asString("Unknown"); if (Arrays.stream(JsonWebKey.KeyType.values()).noneMatch(knownKeyType -> knownKeyType.name().equals(kty))) { return null; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeySerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeySerializer.java index f93d5930a22..10b562bf1b8 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeySerializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeySerializer.java @@ -15,24 +15,24 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; -import java.io.IOException; import java.util.Map; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; + /** * See https://tools.ietf.org/html/rfc7517 */ -public class JsonWebKeySerializer extends JsonSerializer { +public class JsonWebKeySerializer extends ValueSerializer { @Override - public void serialize(JsonWebKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + public void serialize(JsonWebKey value, JsonGenerator gen, SerializationContext serializers) { gen.writeStartObject(); for (Map.Entry entry : value.getKeyProperties().entrySet()) { - gen.writeFieldName(entry.getKey()); - gen.writeObject(entry.getValue()); + gen.writeName(entry.getKey()); + gen.writePOJO(entry.getValue()); } gen.writeEndObject(); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeToken.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeToken.java index 4f1844f61e6..faa80e0a427 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeToken.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeToken.java @@ -13,8 +13,8 @@ */ package org.cloudfoundry.identity.uaa.oauth.token; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.oauth.common.DefaultOAuth2AccessToken; import org.cloudfoundry.identity.uaa.oauth.common.OAuth2AccessToken; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java index a4aa40b3234..47e9fd97576 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java @@ -15,20 +15,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import lombok.Getter; import org.cloudfoundry.identity.uaa.EntityWithAlias; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.util.StringUtils; - +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; import jakarta.validation.constraints.NotNull; import java.io.IOException; import java.util.Date; @@ -322,40 +321,40 @@ public void setSerializeConfigRaw(boolean serializeConfigRaw) { this.serializeConfigRaw = serializeConfigRaw; } - public static class IdentityProviderSerializer extends JsonSerializer { + public static class IdentityProviderSerializer extends ValueSerializer { @Override - public void serialize(IdentityProvider value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + public void serialize(IdentityProvider value, JsonGenerator gen, SerializationContext serializers) { gen.writeStartObject(); - gen.writeStringField(FIELD_TYPE, value.getType()); + gen.writeStringProperty(FIELD_TYPE, value.getType()); if (value.isSerializeConfigRaw()) { - gen.writeObjectField(FIELD_CONFIG, value.getConfig()); + gen.writeObjectProperty(FIELD_CONFIG, value.getConfig()); } else { - gen.writeStringField(FIELD_CONFIG, JsonUtils.writeValueAsString(value.getConfig())); + gen.writeStringProperty(FIELD_CONFIG, JsonUtils.writeValueAsString(value.getConfig())); } - gen.writeStringField(FIELD_ID, value.getId()); - gen.writeStringField(FIELD_ORIGIN_KEY, value.getOriginKey()); - gen.writeStringField(FIELD_NAME, value.getName()); - gen.writeNumberField(FIELD_VERSION, value.getVersion()); + gen.writeStringProperty(FIELD_ID, value.getId()); + gen.writeStringProperty(FIELD_ORIGIN_KEY, value.getOriginKey()); + gen.writeStringProperty(FIELD_NAME, value.getName()); + gen.writeNumberProperty(FIELD_VERSION, value.getVersion()); writeDateField(FIELD_CREATED, value.getCreated(), gen); writeDateField(FIELD_LAST_MODIFIED, value.getLastModified(), gen); - gen.writeBooleanField(FIELD_ACTIVE, value.isActive()); - gen.writeStringField(FIELD_IDENTITY_ZONE_ID, value.getIdentityZoneId()); - gen.writeStringField(FIELD_ALIAS_ID, value.getAliasId()); - gen.writeStringField(FIELD_ALIAS_ZID, value.getAliasZid()); + gen.writeBooleanProperty(FIELD_ACTIVE, value.isActive()); + gen.writeStringProperty(FIELD_IDENTITY_ZONE_ID, value.getIdentityZoneId()); + gen.writeStringProperty(FIELD_ALIAS_ID, value.getAliasId()); + gen.writeStringProperty(FIELD_ALIAS_ZID, value.getAliasZid()); gen.writeEndObject(); } public void writeDateField(String fieldName, Date value, JsonGenerator gen) throws IOException { if (value != null) { - gen.writeNumberField(fieldName, value.getTime()); + gen.writeNumberProperty(fieldName, value.getTime()); } else { - gen.writeNullField(fieldName); + gen.writeNullProperty(fieldName); } } } - public static class IdentityProviderDeserializer extends JsonDeserializer { + public static class IdentityProviderDeserializer extends ValueDeserializer { @Override public IdentityProvider deserialize(JsonParser jp, DeserializationContext ctxt) { IdentityProvider result = new IdentityProvider(); @@ -367,8 +366,8 @@ public IdentityProvider deserialize(JsonParser jp, DeserializationContext ctxt) JsonNode configNode = node.get("config"); if (configNode == null) { config = null; - } else if (configNode.isTextual()) { - config = configNode.textValue(); + } else if (configNode.isString()) { + config = configNode.asString(); } else { config = configNode.toString(); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java index d0764936f2f..013fedd26e0 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java @@ -13,6 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.util.Assert; @@ -28,6 +29,7 @@ public abstract class ScimCore { private ScimMeta meta = new ScimMeta(); + @JsonCreator protected ScimCore(String id) { this.id = id; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java index faede273a1e..e37b0ed5254 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.scim; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonSerialize; import java.util.ArrayList; import java.util.List; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java index 19cab607d42..f4272bd2cd1 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java @@ -16,8 +16,8 @@ import java.util.Date; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java index 7801586de59..4feb187e4fb 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java @@ -34,8 +34,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import lombok.Setter; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java index eb781d6c9ba..7ad79374d75 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java @@ -13,29 +13,28 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.impl; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.exc.UnrecognizedPropertyException; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; +import tools.jackson.databind.ValueDeserializer; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -public class ScimUserJsonDeserializer extends JsonDeserializer { +public class ScimUserJsonDeserializer extends ValueDeserializer { @Override - public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) { ScimUser user = new ScimUser(); while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() == JsonToken.FIELD_NAME) { + if (jp.getCurrentToken() == JsonToken.PROPERTY_NAME) { String fieldName = jp.getCurrentName(); jp.nextToken(); @@ -96,7 +95,7 @@ public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) throws I user.setSalt(jp.readValueAs(String.class)); } else if ("passwordLastModified".equalsIgnoreCase(fieldName)) { if (jp.getValueAsString() != null) { - user.setPasswordLastModified(JsonDateDeserializer.getDate(jp.getValueAsString(), jp.getCurrentLocation())); + user.setPasswordLastModified(JsonDateDeserializer.getDate(jp.getValueAsString(), jp.currentLocation())); } } else if ("approvals".equalsIgnoreCase(fieldName)) { user.setApprovals(new HashSet<>(Arrays.asList(jp.readValueAs(Approval[].class)))); @@ -109,7 +108,7 @@ public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) throws I user.setPreviousLogonTime(jp.getValueAsLong()); } } else { - throw new UnrecognizedPropertyException("unrecognized field", jp.getCurrentLocation(), + throw new UnrecognizedPropertyException("unrecognized field", jp.currentLocation(), ScimUser.class, fieldName, Collections.emptySet()); } } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/account/OpenIdConfigurationTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/account/OpenIdConfigurationTests.java index 5205c21b2ae..306909ac989 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/account/OpenIdConfigurationTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/account/OpenIdConfigurationTests.java @@ -1,11 +1,11 @@ package org.cloudfoundry.identity.uaa.account; -import com.fasterxml.jackson.core.JsonProcessingException; import org.cloudfoundry.identity.uaa.test.JsonTranslation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.json.BasicJsonTester; import org.springframework.test.util.ReflectionTestUtils; +import tools.jackson.core.JacksonException; import java.lang.reflect.Field; @@ -51,7 +51,7 @@ void defaultClaims() { } @Test - void allNulls() throws JsonProcessingException { + void allNulls() throws JacksonException { OpenIdConfiguration openIdConfiguration = new OpenIdConfiguration(null, null); for (Field field : OpenIdConfiguration.class.getDeclaredFields()) { diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/client/UaaClientDetailsTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/client/UaaClientDetailsTest.java index afc486e7195..4eabfb44843 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/client/UaaClientDetailsTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/client/UaaClientDetailsTest.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.client; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -152,7 +154,7 @@ void jsonSerialize() throws Exception { UaaClientDetails details = new UaaClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); details.setClientId("foo"); details.setClientSecret("bar"); - String value = new ObjectMapper().writeValueAsString(details); + String value = new JsonMapper().writeValueAsString(details); assertThat(value).contains("client_id") .contains("client_secret") .contains("authorized_grant_types") @@ -164,14 +166,14 @@ void jsonSerializeAdditionalInformation() throws Exception { UaaClientDetails details = new UaaClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); details.setClientId("foo"); details.setAdditionalInformation(Collections.singletonMap("foo", "bar")); - String value = new ObjectMapper().writeValueAsString(details); + String value = new JsonMapper().writeValueAsString(details); assertThat(value).contains("\"foo\":\"bar\""); } @Test void jsonDeserialize() throws Exception { String value = "{\"foo\":\"bar\",\"client_id\":\"foo\",\"scope\":[\"bar\",\"foo\"],\"authorized_grant_types\":[\"authorization_code\"],\"authorities\":[\"ROLE_USER\"]}"; - UaaClientDetails details = new ObjectMapper().readValue(value, UaaClientDetails.class); + UaaClientDetails details = new JsonMapper().readValue(value, UaaClientDetails.class); UaaClientDetails expected = new UaaClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER"); expected.setAdditionalInformation(Collections.singletonMap("foo", (Object) "bar")); assertThat(details).isEqualTo(expected); @@ -181,7 +183,7 @@ void jsonDeserialize() throws Exception { void jsonDeserializeWithArraysAsStrings() throws Exception { // Collection values can be deserialized from space or comma-separated lists String value = "{\"foo\":\"bar\",\"client_id\":\"foo\",\"scope\":\"bar foo\",\"authorized_grant_types\":\"authorization_code\",\"authorities\":\"ROLE_USER,ROLE_ADMIN\"}"; - UaaClientDetails details = new ObjectMapper().readValue(value, UaaClientDetails.class); + UaaClientDetails details = new JsonMapper().readValue(value, UaaClientDetails.class); UaaClientDetails expected = new UaaClientDetails("foo", "", "foo,bar", "authorization_code", "ROLE_USER,ROLE_ADMIN"); expected.setAdditionalInformation(Collections.singletonMap("foo", (Object) "bar")); assertThat(details).isEqualTo(expected); diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java index 914dff369c0..617333f9d58 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java @@ -14,8 +14,8 @@ package org.cloudfoundry.identity.uaa.impl; -import com.fasterxml.jackson.core.JsonLocation; import org.junit.jupiter.api.Test; +import tools.jackson.core.TokenStreamLocation; import java.io.IOException; import java.text.ParseException; @@ -31,7 +31,7 @@ class JsonDateDeserializerTest { @Test void parsing() throws IOException, ParseException { - Date d = JsonDateDeserializer.getDate(testDateString, new JsonLocation(null, 22, 0, 0)); + Date d = JsonDateDeserializer.getDate(testDateString, new TokenStreamLocation(null, 22, 0, 0)); assertThat((long) d.getTime()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(testDateString).getTime()); } @@ -42,7 +42,7 @@ void parsingParallel() throws InterruptedException { threadArray[i] = new Thread(() -> { try { - Date d = JsonDateDeserializer.getDate(testDateString, new JsonLocation(null, 22, 0, 0)); + Date d = JsonDateDeserializer.getDate(testDateString, new TokenStreamLocation(null, 22, 0, 0)); if (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(testDateString).getTime() != d.getTime()) { throw new Exception("Unexpected date"); } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java index deb7898f2c5..18ea1746010 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java @@ -1,8 +1,8 @@ package org.cloudfoundry.identity.uaa.impl; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; +import tools.jackson.core.JsonGenerator; import org.junit.jupiter.api.Test; +import tools.jackson.core.TokenStreamFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -19,7 +19,7 @@ class JsonDateSerializerTest { void formatting() throws IOException { Date now = new Date(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - JsonGenerator gen = new JsonFactory().createGenerator(bos); + JsonGenerator gen = new TokenStreamFactory().createGenerator(bos); new JsonDateSerializer().serialize(now, gen, null); gen.close(); assertThat(bos).hasToString("\"%s\"".formatted(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(now))); @@ -34,7 +34,7 @@ void formattingParallel() throws InterruptedException { try { Date now = new Date(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - JsonGenerator gen = new JsonFactory().createGenerator(bos); + JsonGenerator gen = new TokenStreamFactory().createGenerator(bos); new JsonDateSerializer().serialize(now, gen, null); gen.close(); if (!"\"%s\"".formatted(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(now)) diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/token/grant/AuthorizationCodeAccessTokenProviderWithConversionTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/token/grant/AuthorizationCodeAccessTokenProviderWithConversionTests.java index d273f6cf8cb..54c2f3c11fc 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/token/grant/AuthorizationCodeAccessTokenProviderWithConversionTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/token/grant/AuthorizationCodeAccessTokenProviderWithConversionTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.client.token.grant; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.cloudfoundry.identity.uaa.oauth.client.grant.AuthorizationCodeAccessTokenProvider; import org.cloudfoundry.identity.uaa.oauth.client.resource.AuthorizationCodeResourceDetails; import org.cloudfoundry.identity.uaa.oauth.client.resource.OAuth2AccessDeniedException; @@ -145,7 +147,7 @@ void getAccessTokenFromJson() throws Exception { final OAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO"); requestFactory = new ClientHttpRequestFactory() { public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { - return new StubClientHttpRequest(new ObjectMapper().writeValueAsString(token)); + return new StubClientHttpRequest(new JsonMapper().writeValueAsString(token)); } }; AccessTokenRequest request = new DefaultAccessTokenRequest(); @@ -162,7 +164,7 @@ void getErrorFromJson() { requestFactory = new ClientHttpRequestFactory() { public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { return new StubClientHttpRequest(HttpStatus.BAD_REQUEST, - new ObjectMapper().writeValueAsString(exception)); + new JsonMapper().writeValueAsString(exception)); } }; AccessTokenRequest request = new DefaultAccessTokenRequest(); diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/JsonSerializationTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/JsonSerializationTests.java index 67adb94cf59..f1f09d1acf1 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/JsonSerializationTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/JsonSerializationTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.common; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidClientException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.OAuth2Exception; import org.junit.jupiter.api.Test; @@ -19,7 +21,7 @@ class JsonSerializationTests { void defaultSerialization() throws Exception { DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); accessToken.setExpiration(new Date(System.currentTimeMillis() + 10000)); - String result = new ObjectMapper().writeValueAsString(accessToken); + String result = new JsonMapper().writeValueAsString(accessToken); // System.err.println(result); assertThat(result).as("Wrong token: " + result).contains("\"token_type\":\"bearer\"") .as("Wrong token: " + result).contains("\"access_token\":\"FOO\"") @@ -31,7 +33,7 @@ void refreshSerialization() throws Exception { DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken("FOO"); accessToken.setRefreshToken(new DefaultOAuth2RefreshToken("BAR")); accessToken.setExpiration(new Date(System.currentTimeMillis() + 10000)); - String result = new ObjectMapper().writeValueAsString(accessToken); + String result = new JsonMapper().writeValueAsString(accessToken); // System.err.println(result); assertThat(result).as("Wrong token: " + result).contains("\"token_type\":\"bearer\"") .as("Wrong token: " + result).contains("\"access_token\":\"FOO\"") @@ -43,7 +45,7 @@ void refreshSerialization() throws Exception { void exceptionSerialization() throws Exception { InvalidClientException exception = new InvalidClientException("FOO"); exception.addAdditionalInformation("foo", "bar"); - String result = new ObjectMapper().writeValueAsString(exception); + String result = new JsonMapper().writeValueAsString(exception); // System.err.println(result); assertThat(result).as("Wrong result: " + result).contains("\"error\":\"invalid_client\"") .as("Wrong result: " + result).contains("\"error_description\":\"FOO\"") @@ -53,7 +55,7 @@ void exceptionSerialization() throws Exception { @Test void defaultDeserialization() throws Exception { String accessToken = "{\"access_token\": \"FOO\", \"expires_in\": 100, \"token_type\": \"mac\"}"; - OAuth2AccessToken result = new ObjectMapper().readValue(accessToken, OAuth2AccessToken.class); + OAuth2AccessToken result = new JsonMapper().readValue(accessToken, OAuth2AccessToken.class); // System.err.println(result); assertThat(result.getValue()).isEqualTo("FOO"); assertThat(result.getTokenType()).isEqualTo("mac"); @@ -63,7 +65,7 @@ void defaultDeserialization() throws Exception { @Test void exceptionDeserialization() throws Exception { String exception = "{\"error\": \"invalid_client\", \"error_description\": \"FOO\", \"foo\": \"bar\"}"; - OAuth2Exception result = new ObjectMapper().readValue(exception, OAuth2Exception.class); + OAuth2Exception result = new JsonMapper().readValue(exception, OAuth2Exception.class); // System.err.println(result); assertThat(result.getMessage()).isEqualTo("FOO"); assertThat(result.getOAuth2ErrorCode()).isEqualTo("invalid_client"); diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2DeserializerTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2DeserializerTests.java index ea23cad6e04..ff9d766b6cb 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2DeserializerTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2DeserializerTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.common; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -20,7 +22,7 @@ class OAuth2AccessTokenJackson2DeserializerTests extends BaseOAuth2AccessTokenJa @BeforeEach void createObjectMapper() { - mapper = new ObjectMapper(); + mapper = new JsonMapper(); } @Test diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java index 52a386e51b4..a06ee674cd9 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java @@ -1,9 +1,11 @@ package org.cloudfoundry.identity.uaa.oauth.common; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import tools.jackson.databind.DatabindException; import java.io.IOException; @@ -20,7 +22,7 @@ class OAuth2AccessTokenJackson2SerializerTests extends BaseOAuth2AccessTokenJack @BeforeEach void createObjectMapper() { - mapper = new ObjectMapper(); + mapper = new JsonMapper(); } @Test @@ -60,11 +62,11 @@ void writeValueAsStringWithNullScope() { accessToken.getScope().add(null); } catch (NullPointerException e) { // short circuit NPE from Java 7 (which is correct but only relevant for this test) - throw new JsonMappingException("Scopes cannot be null or empty. Got [null]"); + throw new DatabindException("Scopes cannot be null or empty. Got [null]"); } mapper.writeValueAsString(accessToken); }) - .isInstanceOf(JsonMappingException.class) + .isInstanceOf(DatabindException.class) .hasMessageContaining("Scopes cannot be null or empty. Got [null]"); } @@ -73,7 +75,7 @@ void writeValueAsStringWithEmptyStringScope() { accessToken.getScope().clear(); accessToken.getScope().add(""); assertThatThrownBy(() -> mapper.writeValueAsString(accessToken)) - .isInstanceOf(JsonMappingException.class) + .isInstanceOf(DatabindException.class) .hasMessageContaining("Scopes cannot be null or empty. Got []"); } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionDeserializerTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionDeserializerTests.java index a2093a54ab8..c7ea08d491f 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionDeserializerTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionDeserializerTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.common.exception; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.cloudfoundry.identity.uaa.oauth.common.exceptions.BadClientCredentialsException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InsufficientScopeException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidClientException; @@ -28,7 +30,7 @@ class OAuth2ExceptionDeserializerTests { @BeforeAll static void setUpClass() { - mapper = new ObjectMapper(); + mapper = new JsonMapper(); } @Test diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionJackson2DeserializerTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionJackson2DeserializerTests.java index 21b721baab8..b98defc4d24 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionJackson2DeserializerTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionJackson2DeserializerTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.common.exception; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InsufficientScopeException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidClientException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidGrantException; @@ -27,7 +29,7 @@ class OAuth2ExceptionJackson2DeserializerTests { @BeforeAll static void setUpClass() { - mapper = new ObjectMapper(); + mapper = new JsonMapper(); } @Test diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionSerializerTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionSerializerTests.java index 49c91cf8380..aad4c877c08 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionSerializerTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/exception/OAuth2ExceptionSerializerTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.common.exception; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.cloudfoundry.identity.uaa.oauth.common.exceptions.BadClientCredentialsException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InsufficientScopeException; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidClientException; @@ -35,7 +37,7 @@ class OAuth2ExceptionSerializerTests { @BeforeAll static void setUpClass() { - mapper = new ObjectMapper(); + mapper = new JsonMapper(); } @AfterEach diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializerTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializerTest.java index 2102bfe4035..6696d243a5e 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializerTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializerTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.jupiter.api.Test; diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyTests.java index 13ee2372fd8..3ce29b1d881 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.jupiter.api.Test; diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupportTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupportTests.java index cbdd6ebef82..9ea3dceed04 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupportTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/OAuth2AccessTokenSupportTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.token; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.cloudfoundry.identity.uaa.oauth.client.resource.ClientCredentialsResourceDetails; import org.cloudfoundry.identity.uaa.oauth.client.resource.OAuth2AccessDeniedException; import org.cloudfoundry.identity.uaa.oauth.common.AuthenticationScheme; @@ -51,7 +53,7 @@ class OAuth2AccessTokenSupportTests { private final AccessTokenRequest request = new DefaultAccessTokenRequest(); - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new JsonMapper(); private final OAuth2AccessTokenSupport support = new OAuth2AccessTokenSupport() { }; diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonMatcher.java b/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonMatcher.java index 6c9c0ae3881..7c9ca600763 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonMatcher.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonMatcher.java @@ -1,10 +1,12 @@ package org.cloudfoundry.identity.uaa.test; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.hamcrest.BaseMatcher; import org.hamcrest.Description; +import tools.jackson.core.JacksonException; import static org.cloudfoundry.identity.uaa.test.ModelTestUtils.getResourceAsString; @@ -22,12 +24,12 @@ static org.hamcrest.Matcher isJsonString(String expectedJson) { } private final String expectedJson; - private JsonProcessingException jsonException; + private JacksonException jsonException; private JsonMatcher(String expectedJson) { this.expectedJson = expectedJson; this.jsonException = null; - this.mapper = new ObjectMapper(); + this.mapper = new JsonMapper(); } @Override @@ -40,7 +42,7 @@ public boolean matches(Object actualJson) { final JsonNode expectedTree = mapper.readTree(expectedJson); return expectedTree.equals(actualTree); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { jsonException = e; return false; } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java b/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java index 2e60a19a5e8..6af526129cd 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java @@ -1,8 +1,10 @@ package org.cloudfoundry.identity.uaa.test; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.junit.jupiter.api.Test; +import tools.jackson.core.JacksonException; import java.io.IOException; @@ -45,7 +47,7 @@ protected void setUp( this.withAllNullFields = withAllNullFields; this.jsonFileName = subjectClass.getSimpleName() + ".json"; - this.objectMapper = new ObjectMapper(); + this.objectMapper = new JsonMapper(); } protected ObjectMapper getObjectMapper() { @@ -58,7 +60,7 @@ private void validate() { } @Test - void toJson() throws JsonProcessingException { + void toJson() throws JacksonException { validate(); assertThat(subjectClass.getResourceAsStream(jsonFileName)).as("file <%s/%s> must exist on classpath".formatted(subjectClass.getPackage().getName().replace(".", "/"), jsonFileName)).isNotNull(); @@ -80,7 +82,7 @@ void fromJson() throws IOException { } @Test - void withNullFields_checkIsEmptyJson() throws IllegalAccessException, InstantiationException, JsonProcessingException { + void withNullFields_checkIsEmptyJson() throws IllegalAccessException, InstantiationException, JacksonException { assumeTrue(EXPECT_EMPTY_JSON.equals(withAllNullFields), "To configure this test, use %s instead of %s".formatted(EXPECT_EMPTY_JSON, withAllNullFields)); validate(); @@ -90,7 +92,7 @@ void withNullFields_checkIsEmptyJson() throws IllegalAccessException, Instantiat } @Test - void withNullFields_compareToFile() throws JsonProcessingException, IllegalAccessException, InstantiationException { + void withNullFields_compareToFile() throws JacksonException, IllegalAccessException, InstantiationException { assumeTrue(EXPECT_NULLS_IN_JSON.equals(withAllNullFields), "To configure this test, use %s instead of %s".formatted(EXPECT_NULLS_IN_JSON, withAllNullFields)); validate(); diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/LoginConsentTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/LoginConsentTest.java index 19023b26a06..016c469196e 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/LoginConsentTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/LoginConsentTest.java @@ -1,13 +1,15 @@ package org.cloudfoundry.identity.uaa.zone; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class LoginConsentTest { - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new JsonMapper(); @Test void testDefaultConstructor() { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java index 97b86c6552b..b114ffb3473 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.account; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java index 92606560c19..31b649a7d73 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.account; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index 6325801ec5c..04b7bb1b04f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.account; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.message.MessageService; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java index 5382919d04a..a86a0bda9ce 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.audit.event; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java index ddc5d9899a0..717d334558d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.audit.event; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.oauth.common.OAuth2AccessToken; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java index 39145472301..088a018f50e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java @@ -13,8 +13,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import lombok.Getter; import lombok.Setter; import lombok.ToString; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java index 3077cc1c84b..3b28e3c9da5 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java @@ -13,15 +13,14 @@ */ package org.cloudfoundry.identity.uaa.authentication; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonMappingException; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.DeserializationContext; import org.springframework.security.core.GrantedAuthority; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.ValueDeserializer; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; @@ -30,9 +29,9 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; -public class UaaAuthenticationDeserializer extends JsonDeserializer implements UaaAuthenticationJsonBase { +public class UaaAuthenticationDeserializer extends ValueDeserializer implements UaaAuthenticationJsonBase { @Override - public UaaAuthentication deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + public UaaAuthentication deserialize(JsonParser jp, DeserializationContext ctxt) { UaaAuthenticationDetails details = null; UaaPrincipal princpal = null; List authorities = emptyList(); @@ -46,10 +45,10 @@ public UaaAuthentication deserialize(JsonParser jp, DeserializationContext ctxt) String idpIdToken = null; Map> userAttributes = emptyMap(); while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() == JsonToken.FIELD_NAME) { + if (jp.getCurrentToken() == JsonToken.PROPERTY_NAME) { String fieldName = jp.getCurrentName(); jp.nextToken(); - if (NULL_STRING.equals(jp.getText())) { + if (NULL_STRING.equals(jp.getString())) { //do nothing } else if (DETAILS.equals(fieldName)) { details = jp.readValueAs(UaaAuthenticationDetails.class); @@ -85,7 +84,7 @@ public UaaAuthentication deserialize(JsonParser jp, DeserializationContext ctxt) } } if (princpal == null) { - throw new JsonMappingException("Missing " + UaaPrincipal.class.getName()); + throw new DatabindException("Missing " + UaaPrincipal.class.getName()); } UaaAuthentication uaaAuthentication = new UaaAuthentication(princpal, null, diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java index 1c6ca4a79c6..471cecb9304 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java @@ -13,30 +13,28 @@ */ package org.cloudfoundry.identity.uaa.authentication; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; -import java.io.IOException; - -public class UaaAuthenticationSerializer extends JsonSerializer implements UaaAuthenticationJsonBase { +public class UaaAuthenticationSerializer extends ValueSerializer implements UaaAuthenticationJsonBase { @Override - public void serialize(UaaAuthentication value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + public void serialize(UaaAuthentication value, JsonGenerator gen, SerializationContext serializers) { gen.writeStartObject(); if (value.getDetails() instanceof UaaAuthenticationDetails) { - gen.writeObjectField(DETAILS, value.getDetails()); + gen.writeObjectProperty(DETAILS, value.getDetails()); } - gen.writeObjectField(PRINCIPAL, value.getPrincipal()); - gen.writeObjectField(AUTHORITIES, serializeAuthorites(value.getAuthorities())); - gen.writeObjectField(EXTERNAL_GROUPS, value.getExternalGroups()); - gen.writeNumberField(EXPIRES_AT, value.getExpiresAt()); - gen.writeNumberField(AUTH_TIME, value.getAuthenticatedTime()); - gen.writeBooleanField(AUTHENTICATED, value.isAuthenticated()); - gen.writeObjectField(PREVIOIUS_LOGIN_SUCCESS_TIME, value.getLastLoginSuccessTime()); - gen.writeObjectField(USER_ATTRIBUTES, value.getUserAttributesAsMap()); - gen.writeObjectField(AUTHENTICATION_METHODS, value.getAuthenticationMethods()); - gen.writeObjectField(AUTHN_CONTEXT_CLASS_REF, value.getAuthContextClassRef()); - gen.writeObjectField(IDP_ID_TOKEN, value.getIdpIdToken()); + gen.writeObjectProperty(PRINCIPAL, value.getPrincipal()); + gen.writeObjectProperty(AUTHORITIES, serializeAuthorites(value.getAuthorities())); + gen.writeObjectProperty(EXTERNAL_GROUPS, value.getExternalGroups()); + gen.writeNumberProperty(EXPIRES_AT, value.getExpiresAt()); + gen.writeNumberProperty(AUTH_TIME, value.getAuthenticatedTime()); + gen.writeBooleanProperty(AUTHENTICATED, value.isAuthenticated()); + gen.writeObjectProperty(PREVIOIUS_LOGIN_SUCCESS_TIME, value.getLastLoginSuccessTime()); + gen.writeObjectProperty(USER_ATTRIBUTES, value.getUserAttributesAsMap()); + gen.writeObjectProperty(AUTHENTICATION_METHODS, value.getAuthenticationMethods()); + gen.writeObjectProperty(AUTHN_CONTEXT_CLASS_REF, value.getAuthContextClassRef()); + gen.writeObjectProperty(IDP_ID_TOKEN, value.getIdpIdToken()); gen.writeEndObject(); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java index e11f0fc39ef..db03a5f9cca 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaLoginHint.java @@ -1,9 +1,9 @@ package org.cloudfoundry.identity.uaa.authentication; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.core.JacksonException; -import java.io.IOException; import java.io.Serial; import java.io.Serializable; import java.net.URLDecoder; @@ -13,7 +13,7 @@ public class UaaLoginHint implements Serializable { @Serial private static final long serialVersionUID = 4021539346161285037L; private String origin; - private static ObjectMapper mapper = new ObjectMapper(); + private static ObjectMapper mapper = new JsonMapper(); public static UaaLoginHint parseRequestParameter(String loginHint) { if (loginHint == null) { @@ -22,7 +22,7 @@ public static UaaLoginHint parseRequestParameter(String loginHint) { try { loginHint = URLDecoder.decode(loginHint, StandardCharsets.UTF_8); return mapper.readValue(loginHint, UaaLoginHint.class); - } catch (IOException e) { + } catch (JacksonException e) { return null; } } @@ -46,7 +46,7 @@ public void setOrigin(String origin) { public String toString() { try { return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { return super.toString(); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java index b76a1202999..513c8cd6add 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java index 6a45efb78d3..a5674bf5746 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.authentication.manager; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.login.AutologinRequest; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.LinkedMaskingMultiValueMap; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java index 9bd3c507f9a..1ce1e95b486 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcClientMetadataProvisioning.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.client; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java index 7ddaf8ff859..12864d7c755 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonDeserialize; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java index d0be7a3ae02..c6c5a972642 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java @@ -13,8 +13,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.error; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.OAuth2Exception; import java.util.Map; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java new file mode 100644 index 00000000000..3a7bf851820 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java @@ -0,0 +1,44 @@ +package org.cloudfoundry.identity.uaa.impl.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.cfg.ConstructorDetector; +import tools.jackson.databind.cfg.DateTimeFeature; +import tools.jackson.databind.json.JsonMapper; + +import java.util.List; + +@Configuration +public class JacksonMapperCustomizer implements WebMvcConfigurer { + + @Bean + @Primary + public JsonMapper jsonMapper() { + return JsonMapper.builder() + .enable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .constructorDetector(ConstructorDetector.DEFAULT + .withAllowImplicitWithDefaultConstructor(false)) + .build(); + } + + @Override + public void extendMessageConverters(List> converters) { + JsonMapper mapper = jsonMapper(); + for (int i = 0; i < converters.size(); i++) { + if (converters.get(i) instanceof JacksonJsonHttpMessageConverter) { + converters.set(i, new JacksonJsonHttpMessageConverter(mapper)); + break; + } + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java index eaac83dbd49..e66afe37e56 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.invitations; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AuthorizationAttributesParser.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AuthorizationAttributesParser.java index 09e7ce0e30c..5cf420650f2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AuthorizationAttributesParser.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AuthorizationAttributesParser.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.cloudfoundry.identity.uaa.util.JsonUtils; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java index 19fb952e658..f7b37a86077 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyHelper.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyHelper.java index c1c57ba01bf..f9151905cfc 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyHelper.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyHelper.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.JWK; import org.cloudfoundry.identity.uaa.util.JsonUtils; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java index dc6ef353eb8..48d6c3cf09d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth.jwt; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -52,6 +53,7 @@ public class HeaderParameters { } + @JsonCreator HeaderParameters(String alg, String kid, String jku) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java index 326b9057053..40589aa85a2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.provider.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OidcMetadataFetcher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OidcMetadataFetcher.java index 713dbf01d7a..815b20e2268 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OidcMetadataFetcher.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OidcMetadataFetcher.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.provider.oauth; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.identity.uaa.cache.UrlContentCache; @@ -18,8 +19,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import tools.jackson.core.JacksonException; -import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -134,8 +135,8 @@ private OidcMetadata fetchMetadata(URL discoveryUrl, boolean shouldDoSslValidati rawContents = contentCache.getUrlContent(discoveryUrl.toString(), nonTrustingRestTemplate); } try { - return new ObjectMapper().readValue(rawContents, OidcMetadata.class); - } catch (IOException e) { + return new JsonMapper().readValue(rawContents, OidcMetadata.class); + } catch (JacksonException e) { throw new OidcMetadataFetchingException(e); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/core/http/CredentialIdTypeJWTjsonField.java b/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/core/http/CredentialIdTypeJWTjsonField.java index 93d3d1089f0..eb363017c40 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/core/http/CredentialIdTypeJWTjsonField.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/core/http/CredentialIdTypeJWTjsonField.java @@ -4,10 +4,8 @@ import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.identity.uaa.ratelimiting.core.config.exception.RateLimitingConfigException; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -39,7 +37,7 @@ public AuthorizationCredentialIdExtractor factory(String keyTypeParameters) { } static class SectionFieldJWT extends SectionJWT { - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper = new JsonMapper(); private final AuthorizationCredentialIdExtractorErrorLogger errorLogger; private final String field; @@ -66,7 +64,7 @@ protected String from(JWTparts jp) { valueFound = value.toString(); } } - catch (JsonProcessingException | RuntimeException e) { + catch (RuntimeException e) { errorLogger.log(e); } return valueFound == null ? null : ("|" + valueFound + "|"); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java b/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java index 0fb0ddb784e..81951db97a5 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java @@ -6,13 +6,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import lombok.Builder; import lombok.Getter; import lombok.ToString; import org.cloudfoundry.identity.uaa.ratelimiting.internal.common.InternalLimiterFactoriesSupplier; import org.cloudfoundry.identity.uaa.ratelimiting.util.NanoTimeSupplier; +import tools.jackson.core.JacksonException; import static com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -75,7 +76,7 @@ public String toString() { try { json = OM.writerWithDefaultPrettyPrinter().writeValueAsString(this); } - catch (JsonProcessingException e) { + catch (JacksonException e) { json = "JsonProcessingException (" + e.getMessage() + "): " + "current: " + current + "fromSource: " + fromSource; @@ -115,5 +116,5 @@ public static RateLimiterStatus noRateLimiting(long now) { return builder().current(Current.builder().status(CurrentStatus.DISABLED).asOf(now).build()).build(); } - private static final ObjectMapper OM = new ObjectMapper(); + private static final ObjectMapper OM = new JsonMapper(); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java index d742b24742e..2bab13a786b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.account.EmailChange; import org.cloudfoundry.identity.uaa.account.EmailChangeResponse; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java index 0e16b75ce08..c33cfea5cf4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java @@ -15,7 +15,7 @@ import java.util.Map; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.oauth.UaaOauth2Authentication; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java index 931810129f4..fc12bca4c27 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java @@ -51,6 +51,7 @@ public enum UaaAuthority implements GrantedAuthority { @Getter private final String userType; + @JsonCreator UaaAuthority(String userType, int value) { this.userType = userType; this.value = value; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java index 6d4639146a3..4365935faf8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaHttpRequestUtils.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.util; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import jakarta.servlet.http.HttpServletRequest; import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; import org.apache.hc.client5.http.HttpRequestRetryStrategy; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/CurrentUserCookieRequestFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/CurrentUserCookieRequestFilterTest.java index 8d3ba6dd1c4..8a3a7f2ba8e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/CurrentUserCookieRequestFilterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/CurrentUserCookieRequestFilterTest.java @@ -99,8 +99,8 @@ void whenCurrentUserExceptionOccurs_respondWithInternalServerError() throws Curr assertThat(res.getStatus()).isEqualTo(500); assertThat(res.getContentType()).isEqualTo("application/json"); - assertThat(JsonUtils.readTree(res.getContentAsString()).get("error").textValue()).isEqualTo("current_user_cookie_error"); - assertThat(JsonUtils.readTree(res.getContentAsString()).get("error_description").textValue()).isEqualTo("There was a problem while creating the Current-User cookie for user id user-guid"); + assertThat(JsonUtils.readTree(res.getContentAsString()).get("error").asString()).isEqualTo("current_user_cookie_error"); + assertThat(JsonUtils.readTree(res.getContentAsString()).get("error_description").asString()).isEqualTo("There was a problem while creating the Current-User cookie for user id user-guid"); verifyNoInteractions(filterChain); } } \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/CurrentUserCookieFactoryTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/CurrentUserCookieFactoryTest.java index 1ac83289cb6..e0300c63418 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/CurrentUserCookieFactoryTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/CurrentUserCookieFactoryTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.login; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.jupiter.api.BeforeEach; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/DeprecatedUaaTokenServicesTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/DeprecatedUaaTokenServicesTests.java index 7f087ef22c0..6e74a7cc998 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/DeprecatedUaaTokenServicesTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/DeprecatedUaaTokenServicesTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java index 290d4566e00..b2ae9d9f857 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth.approval; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.unboundid.scim.sdk.AttributePath; import com.unboundid.scim.sdk.SCIMFilter; import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/RsaJsonWebKeyTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/RsaJsonWebKeyTests.java index b5ebd800473..70732d44c71 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/RsaJsonWebKeyTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwk/RsaJsonWebKeyTests.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.oauth.jwk; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jose.util.BigIntegerUtils; import org.apache.commons.collections4.map.HashedMap; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwt/JwtHeaderHelperTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwt/JwtHeaderHelperTest.java index cadc79c139f..48973734fd0 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwt/JwtHeaderHelperTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/jwt/JwtHeaderHelperTest.java @@ -1,7 +1,8 @@ package org.cloudfoundry.identity.uaa.oauth.jwt; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.ObjectNode; import org.cloudfoundry.identity.uaa.test.RandomParametersJunitExtension; import org.cloudfoundry.identity.uaa.test.RandomParametersJunitExtension.RandomValue; import org.junit.jupiter.api.BeforeEach; @@ -33,7 +34,7 @@ class JWS { @BeforeEach void setup() { - objectNode = new ObjectMapper().createObjectNode(); + objectNode = new JsonMapper().createObjectNode(); objectNode.put("kid", "key-id"); objectNode.put("alg", "key-alg"); objectNode.put("enc", "key-encoding"); @@ -114,7 +115,7 @@ void shouldSerializeOnlyWithValidRequiredHeaders() { class OptionalHeaders { @BeforeEach void setup() { - objectNode = new ObjectMapper().createObjectNode(); + objectNode = new JsonMapper().createObjectNode(); } @ParameterizedTest diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java index f35422ec0b3..00298a949d8 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth.token.matchers; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.oauth.KeyInfoService; import org.cloudfoundry.identity.uaa.oauth.common.OAuth2AccessToken; import org.cloudfoundry.identity.uaa.oauth.common.OAuth2RefreshToken; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java index 225813b5622..6dd31a442e1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.provider.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.github.benmanes.caffeine.cache.Ticker; import com.nimbusds.jose.JWSSigner; import org.apache.commons.codec.binary.Base64; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java index 4f4ed1092e3..d7bc30fa277 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import net.shibboleth.shared.xml.SerializeSupport; import org.cloudfoundry.identity.uaa.provider.saml.OpenSaml5AuthenticationProvider.ResponseToken; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -501,7 +502,7 @@ void authenticateWhenAssertionContainsAttributesThenItSucceeds() { // gh-11785 @Test void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = new JsonMapper(); ClassLoader loader = getClass().getClassLoader(); mapper.registerModules(SecurityJackson2Modules.getModules(loader)); Response response = response(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java index 205aaf31290..b9194720e1d 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import net.shibboleth.shared.xml.SerializeSupport; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.cloudfoundry.identity.uaa.impl.config.RestTemplateConfig; @@ -259,7 +260,7 @@ void authenticateWhenAssertionContainsAttributesThenItSucceeds() { // gh-11785 @Test void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = new JsonMapper(); ClassLoader loader = getClass().getClassLoader(); mapper.registerModules(SecurityJackson2Modules.getModules(loader)); Assertion assertion = assertion(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java index bcedc50b71a..647671c2872 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/client/ClientMetadataAdminEndpointsMockMvcTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.client; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.assertj.core.groups.Tuple; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.test.TestClient; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java index 14ab5ab91e7..05e3e69792a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java @@ -13,7 +13,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException; @@ -137,7 +138,7 @@ void listClientsWithExtremePaginationDefaultsTo500() throws Exception { ResponseEntity result = serverRunning.getForString("/oauth/clients?count=3000", myHeaders); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - SearchResults searchResults = new ObjectMapper().readValue(result.getBody(), SearchResults.class); + SearchResults searchResults = new JsonMapper().readValue(result.getBody(), SearchResults.class); assertThat(searchResults.getItemsPerPage()).isEqualTo(500); assertThat(searchResults.getResources()).hasSize(500); assertThat(searchResults.getTotalResults()).isGreaterThan(500); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java index d6b447a6a30..f393e45f407 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegrationTests.java @@ -13,7 +13,7 @@ */ package org.cloudfoundry.identity.uaa.integration; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordGrantIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordGrantIntegrationTests.java index 5a0bbc26842..c15a481af95 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordGrantIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordGrantIntegrationTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.integration; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ImplicitGrantIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ImplicitGrantIT.java index 4df5373a496..073ed7853dd 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ImplicitGrantIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/ImplicitGrantIT.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java index e8b80955a0c..beff7c2da22 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import org.cloudfoundry.identity.uaa.ServerRunningExtension; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/PrivateKeyJwtClientAuthIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/PrivateKeyJwtClientAuthIT.java index cbed9ea03d9..a251a3bf161 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/PrivateKeyJwtClientAuthIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/PrivateKeyJwtClientAuthIT.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.integration.feature; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginCustomUserAttributesIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginCustomUserAttributesIT.java index 0ceb61a7b76..248664bb78d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginCustomUserAttributesIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginCustomUserAttributesIT.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginEmailIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginEmailIT.java index 19fcce0bd45..22f6a5aa22c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginEmailIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginEmailIT.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java index a4a4ed83e54..eed24cf4c05 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.invitations; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcZonePathTests.java index 33a4a9921f8..d72b0f38510 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcZonePathTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.invitations; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointDocs.java index 18802ee195c..461c411c6e3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointDocs.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.login; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.mock.EndpointDocs; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 745b2357abb..cd0ea90d0aa 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.audit; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.commons.codec.binary.Base64; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index 436b18ad07e..8f48a2ea560 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.clients; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import org.cloudfoundry.identity.uaa.DefaultTestContext; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcZonePathTests.java index 0de0225ba36..ff20e8fdc3a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcZonePathTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.clients; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import org.cloudfoundry.identity.uaa.DefaultTestContext; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java index 02b6971f72a..02abd502624 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/AbstractLdapMockMvcTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.ldap; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Sets; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapProviderDefinitionDeserializeTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapProviderDefinitionDeserializeTests.java index edc64b7a7a6..81fead4d615 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapProviderDefinitionDeserializeTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapProviderDefinitionDeserializeTests.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.mock.ldap; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsAliasMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsAliasMockMvcTests.java index d87986d79a1..f17b231d983 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsAliasMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsAliasMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.providers; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.constants.ClientAuthentication; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java index e47f0c0db76..e0fbb4bbec7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.providers; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.lang3.RandomStringUtils; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java index 437601bb5df..2c5ac30c598 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.providers; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.lang3.RandomStringUtils; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/JwtBearerGrantMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/JwtBearerGrantMockMvcTests.java index 7a646259bdc..4c640f4cda4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/JwtBearerGrantMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/JwtBearerGrantMockMvcTests.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.util.JwtTokenUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/ListUserTokenMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/ListUserTokenMockMvcTests.java index bea5ea6ebfc..bf850faae84 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/ListUserTokenMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/ListUserTokenMockMvcTests.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/RefreshTokenMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/RefreshTokenMockMvcTests.java index f0b332cf286..33d5a2f7c12 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/RefreshTokenMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/RefreshTokenMockMvcTests.java @@ -13,7 +13,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import org.apache.hc.core5.http.HttpStatus; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 44dc08ba084..df52518327b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import jakarta.servlet.http.HttpSession; import org.apache.commons.collections4.map.HashedMap; import org.cloudfoundry.identity.uaa.DefaultTestContext; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockZonePathTests.java index 39f3a23d6c2..95ff220c63f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockZonePathTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import jakarta.servlet.http.HttpSession; import org.apache.commons.collections4.map.HashedMap; import org.cloudfoundry.identity.uaa.DefaultTestContext; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcTest.java index dbc3802cd93..bdf3187fe58 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcZonePathTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcZonePathTest.java index da1e2787e7f..2106693dbfe 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcZonePathTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenRevocationEndpointMockMvcZonePathTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/UserTokenMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/UserTokenMockMvcTests.java index 33718fd6993..4aac5db2b8c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/UserTokenMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/UserTokenMockMvcTests.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.mock.token; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/JwtTokenUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/JwtTokenUtils.java index f1a0890c06a..1b358c2b077 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/JwtTokenUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/JwtTokenUtils.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.util; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.oauth.KeyInfoService; import org.cloudfoundry.identity.uaa.oauth.common.exceptions.InvalidTokenException; import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 360ba5bde73..703d122ffdb 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.mock.util; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.RandomStringUtils; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index cbd368ae653..78806b91ced 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -1,7 +1,7 @@ package org.cloudfoundry.identity.uaa.mock.zones; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JsonNode; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalStore; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java index 6d6ace3b8e8..01839f5a84d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java @@ -1,7 +1,7 @@ package org.cloudfoundry.identity.uaa.mock.zones; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JsonNode; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalStore; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/ZonesWriteScopeMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/ZonesWriteScopeMockMvcTest.java index 2ee02ec750e..19b404e2bf4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/ZonesWriteScopeMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/ZonesWriteScopeMockMvcTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.zones; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; @@ -78,7 +78,7 @@ void getZoneByIdWithZonesWriteScope() throws Exception { String jsonResponse = result.getResponse().getContentAsString(); JsonNode responseNode = JsonUtils.readTree(jsonResponse); - assertThat(responseNode.get("id").asText()).isEqualTo(zone.getId()); + assertThat(responseNode.get("id").asString()).isEqualTo(zone.getId()); mockMvc.perform( get("/identity-zones/" + zone2.getId()) @@ -112,7 +112,7 @@ void getZonesWithZonesWriteScope() throws Exception { String jsonResponse = result.getResponse().getContentAsString(); JsonNode responseNode = JsonUtils.readTree(jsonResponse); - assertThat(responseNode.get(0).get("id").asText()).isEqualTo(zone.getId()); + assertThat(responseNode.get(0).get("id").asString()).isEqualTo(zone.getId()); assertThat(responseNode.get(1)).isNull(); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcTest.java index 0b7ba3b72bc..ab64f335b88 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.mock.token.AbstractTokenMockMvcTests; import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcZonePathTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcZonePathTest.java index bde282c8501..5b89f0ae378 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcZonePathTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointMockMvcZonePathTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.ZoneResolutionMode; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/IntrospectEndpointMockMvcZonePathTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/IntrospectEndpointMockMvcZonePathTest.java index 7c2f80fbcab..c790eea04ea 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/IntrospectEndpointMockMvcZonePathTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/IntrospectEndpointMockMvcZonePathTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.ZoneResolutionMode; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java index 99ca2ba04eb..b2f6ec3f51f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java index 8b5a4e410db..8c867feac79 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointDocs.java index 043ed6e07b8..c5416b7a419 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointDocs.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.apache.commons.lang3.ArrayUtils; import org.cloudfoundry.identity.uaa.mock.EndpointDocs; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 22b5d9d37d3..d1686563ded 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -1,8 +1,8 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; import com.google.common.collect.Sets; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.DefaultTestContext; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java index faf446f23d3..59ac379d774 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcZonePathTests.java @@ -1,8 +1,8 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; import com.google.common.collect.Sets; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.DefaultTestContext; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java index 1df3b5d86b1..84ee99c50f0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.DefaultTestContext; import org.cloudfoundry.identity.uaa.HttpMethodEnum; import org.cloudfoundry.identity.uaa.alias.AliasMockMvcTestBase; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java index 92951144e50..7e395dd5b41 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import org.apache.commons.lang3.RandomStringUtils; import org.apache.hc.core5.http.NameValuePair; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcZonePathTests.java index 727eab6015b..8f3f3ce4ed7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcZonePathTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import com.google.common.collect.Lists; import org.apache.commons.lang3.RandomStringUtils; import org.apache.hc.core5.http.NameValuePair; From ecbd4b761e9c4dd898b120589801ebd7110ee040 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 29 Apr 2026 00:34:11 +0300 Subject: [PATCH 37/58] Remove Jackson 2 dependencies and global Jackson 3 exclusions - Remove global exclusions for tools.jackson.core and tools.jackson (we now use Jackson 3 directly) - Remove spring-boot-jackson exclusion (Jackson 3 auto-config is wanted) - Switch jacksonDatabind to tools.jackson.core:jackson-databind - Switch jacksonDataformatYaml to tools.jackson.dataformat:jackson-dataformat-yaml - Remove spring-boot-jackson2 dependency from server and uaa modules --- build.gradle | 3 --- gradle/libs.versions.toml | 5 ++--- server/build.gradle | 1 - uaa/build.gradle | 1 - 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index c31c93516ba..957ba087867 100644 --- a/build.gradle +++ b/build.gradle @@ -68,9 +68,6 @@ subprojects { exclude(group: "org.hamcrest", module: "hamcrest-core") exclude(group: "org.hamcrest", module: "hamcrest-library") exclude(group: "org.springframework.boot", module: "spring-boot-starter-logging") - exclude(group: "org.springframework.boot", module: "spring-boot-jackson") - exclude(group: "tools.jackson.core") - exclude(group: "tools.jackson") exclude(group: "org.apache.directory.server", module: "apacheds-core") exclude(group: "org.apache.directory.server", module: "apacheds-protocol-ldap") exclude(group: "org.skyscreamer", module: "jsonassert") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 757115e6543..ab031a20c9f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -76,8 +76,8 @@ eclipseJgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version.ref = "ecl # FasterXML Jackson jacksonAnnotations = { module = "com.fasterxml.jackson.core:jackson-annotations" } -jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind" } -jacksonDataformatYaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml" } +jacksonDatabind = { module = "tools.jackson.core:jackson-databind" } +jacksonDataformatYaml = { module = "tools.jackson.dataformat:jackson-dataformat-yaml" } # Flyway flywayCore = { module = "org.flywaydb:flyway-core" } @@ -165,7 +165,6 @@ springWebMvc = { module = "org.springframework:spring-webmvc" } # Spring Boot springBootBom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springBoot" } springBootFlyway = { module = "org.springframework.boot:spring-boot-flyway" } -springBootJackson2 = { module = "org.springframework.boot:spring-boot-jackson2" } springBootJdbc = { module = "org.springframework.boot:spring-boot-jdbc" } springBootSql = { module = "org.springframework.boot:spring-boot-sql" } springBootStarter = { module = "org.springframework.boot:spring-boot-starter" } diff --git a/server/build.gradle b/server/build.gradle index 06b61057903..3cb999bcfee 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -41,7 +41,6 @@ dependencies { // OpenAPI documentation implementation(libs.springdocOpenapi) - implementation(libs.springBootJackson2) implementation(libs.aspectJRt) implementation(libs.aspectJWeaver) diff --git a/uaa/build.gradle b/uaa/build.gradle index b199de5949b..c25c1aeeeee 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -59,7 +59,6 @@ dependencies { // OpenAPI documentation implementation(libs.springDocOpenapi) - implementation(libs.springBootJackson2) implementation(libs.springWeb) implementation(libs.springWebMvc) From fcf6df0c42586612ee8d6cbf3ea8e8399aac1e2a Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 29 Apr 2026 00:34:52 +0300 Subject: [PATCH 38/58] Configure ObjectMapper for Jackson 2 backward compatibility Jackson 3 changed several defaults from Jackson 2. Configure both JsonUtils and JacksonMapperCustomizer to restore Jackson 2 behavior: - DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS: was true (Jackson 2), now false - SerializationFeature.FAIL_ON_EMPTY_BEANS: was true (Jackson 2), now false - DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES: was false (Jackson 2), now true - MapperFeature.SORT_PROPERTIES_ALPHABETICALLY: was false (Jackson 2), now true - ConstructorDetector: prevent Jackson 3 from preferring multi-param constructors when a no-arg constructor exists Also catch JacksonException in convertValue (Jackson 3 throws MismatchedInputException directly instead of wrapping in IllegalArgumentException). --- .../identity/uaa/util/JsonUtils.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java index 8c659d250e3..4e70244e1c9 100644 --- a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java +++ b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java @@ -17,8 +17,13 @@ import tools.jackson.core.JsonParser; import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.JsonNode; +import tools.jackson.databind.MapperFeature; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.cfg.ConstructorDetector; +import tools.jackson.databind.cfg.DateTimeFeature; import tools.jackson.databind.json.JsonMapper; import tools.jackson.core.JacksonException; @@ -27,7 +32,14 @@ import java.util.Map; public class JsonUtils { - private static final ObjectMapper objectMapper = new JsonMapper(); + private static final ObjectMapper objectMapper = JsonMapper.builder() + .enable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .constructorDetector(ConstructorDetector.DEFAULT + .withAllowImplicitWithDefaultConstructor(false)) + .build(); private JsonUtils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); @@ -113,7 +125,7 @@ public static T convertValue(Object object, Class toClazz) throws JsonUti } else { return objectMapper.convertValue(object, toClazz); } - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | JacksonException e) { throw new JsonUtilException(e); } } From aca397b3a0be6cecef7ec63a8830ddf7a7a66639 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 29 Apr 2026 00:35:26 +0300 Subject: [PATCH 39/58] Remove spring-mvc.properties and @PropertySource from WebConfig The spring.jackson.serialization.write-dates-as-timestamps property is no longer valid in Jackson 3/Spring Boot 4. The date timestamp behavior is now configured directly in the ObjectMapper via DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS in JacksonMapperCustomizer. Also remove the javadoc and @PropertySource annotation since the property file no longer exists. --- .../identity/uaa/web/WebConfig.java | 6 ----- .../src/main/resources/spring-mvc.properties | 22 ------------------- 2 files changed, 28 deletions(-) delete mode 100644 server/src/main/resources/spring-mvc.properties diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java index 6492f11b50e..73418dd941e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/web/WebConfig.java @@ -2,20 +2,14 @@ import org.cloudfoundry.identity.uaa.authentication.manager.AutologinRequestConverter; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; -/** - * Web stack configuration. It relies on Spring Boot's Web MVC auto-configuration, - * with a few adjustments in a properties file to match the legacy behavior from UAA. - */ @Configuration @EnableWebMvc -@PropertySource("classpath:spring-mvc.properties") class WebConfig implements WebMvcConfigurer { @Override diff --git a/server/src/main/resources/spring-mvc.properties b/server/src/main/resources/spring-mvc.properties deleted file mode 100644 index 4e089ef848c..00000000000 --- a/server/src/main/resources/spring-mvc.properties +++ /dev/null @@ -1,22 +0,0 @@ -# ----------------------------------------------------------------------------- -# Spring Boot has some defaults that differ from non-Boot Spring. This file -# configures Boot to match the behaviors from previous xml-based configuration. - - -# ----------------------------------------------------------------------------- -# In a Spring @Controller, when you set values in the Model object and issue a -# redirect with `return "redirect:foobar"`, Spring will append Model attributes -# to the redirect URI by default. Spring Boot does not do this and needs to be -# configured. -# -# See for example InvitationsController#processErrorReload -# Permalink: https://github.com/cloudfoundry/uaa/blob/a7a98407d627857ffdf637f94c3b5763cd3017dc/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java#L300-L302 -# This property is deprecated: Deprecated for removal in Spring MVC -# spring.mvc.ignore-default-model-on-redirect=false - - -# ----------------------------------------------------------------------------- -# In Spring, defaults from Jackson are used. Dates (e.g. "2025-02-06T11:04:12") -# are serialized as timestamps, integers that look like 1339478482000000. In -# Spring Boot, by default they are issued as raw strings. -spring.jackson.serialization.write-dates-as-timestamps=true \ No newline at end of file From e12d5181a709fb01cc69c474701cdfc51ae7d25d Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 29 Apr 2026 00:36:20 +0300 Subject: [PATCH 40/58] Fix Jackson 3 API changes not covered by OpenRewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manual corrections for Jackson 3 API changes that the OpenRewrite recipe either missed or got wrong: - getCurrentToken() → currentToken(), getCurrentName() → currentName() - writeObjectProperty() → writePOJOProperty() for writing POJO fields - SimpleType.construct() removed; use ctxt.constructType() instead - JsonUtils.readTree(parser) → parser.readValueAsTree() in deserializers - StreamReadException constructor changed: use (parser, message, cause) - UnrecognizedPropertyException: use .from(parser, ...) factory method - DatabindException: use .from(parser, message) factory method - Remove leftover IOException imports - Remove empty @JsonSerialize/@JsonDeserialize on ExpiringCode - Replace deprecated @JsonSerialize(include=) with @JsonInclude - Remove deprecated include= parameter from @JsonSerialize on ScimMeta --- .../impl/ApprovalsJsonDeserializer.java | 4 ++-- .../Jackson2ArrayOrStringDeserializer.java | 10 +++++++--- .../identity/uaa/codestore/ExpiringCode.java | 4 ---- .../uaa/impl/JsonDateDeserializer.java | 8 +++----- .../OAuth2AccessTokenJackson2Deserializer.java | 5 ++--- .../OAuth2AccessTokenJackson2Serializer.java | 2 +- .../OAuth2ExceptionJackson2Deserializer.java | 4 ++-- .../uaa/oauth/jwk/JsonWebKeyDeserializer.java | 2 +- .../uaa/provider/IdentityProvider.java | 7 +++---- .../identity/uaa/scim/ScimGroup.java | 4 ++-- .../identity/uaa/scim/ScimMeta.java | 4 ++-- .../identity/uaa/scim/ScimUser.java | 3 ++- .../scim/impl/ScimUserJsonDeserializer.java | 8 ++++---- .../UaaAuthenticationDeserializer.java | 6 +++--- .../UaaAuthenticationSerializer.java | 18 +++++++++--------- 15 files changed, 43 insertions(+), 46 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java index 44d6ecbdde6..8b0e11845f4 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java @@ -27,8 +27,8 @@ public class ApprovalsJsonDeserializer extends ValueDeserializer { public Approval deserialize(JsonParser jp, DeserializationContext ctxt) { Approval approval = new Approval(); while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() == JsonToken.PROPERTY_NAME) { - String fieldName = jp.getCurrentName(); + if (jp.currentToken() == JsonToken.PROPERTY_NAME) { + String fieldName = jp.currentName(); jp.nextToken(); if ("userId".equalsIgnoreCase(fieldName)) { approval.setUserId(jp.readValueAs(String.class)); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java index 72608ccf0fe..f07aca709f5 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/client/Jackson2ArrayOrStringDeserializer.java @@ -6,7 +6,6 @@ import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.JavaType; import tools.jackson.databind.deser.std.StdDeserializer; -import tools.jackson.databind.type.SimpleType; import org.springframework.util.StringUtils; import java.util.Arrays; @@ -29,12 +28,17 @@ public Jackson2ArrayOrStringDeserializer() { @Override public JavaType getValueType() { - return SimpleType.construct(String.class); + return getValueType(null); + } + + @Override + public JavaType getValueType(DeserializationContext ctxt) { + return ctxt != null ? ctxt.constructType(String.class) : super.getValueType(ctxt); } @Override public Set deserialize(JsonParser jp, DeserializationContext ctxt) { - JsonToken token = jp.getCurrentToken(); + JsonToken token = jp.currentToken(); if (token.isScalarValue()) { String list = jp.getString(); list = list.replaceAll("\\s+", ","); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java index 792d7cf40f3..549ba49cc93 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java @@ -14,16 +14,12 @@ package org.cloudfoundry.identity.uaa.codestore; import com.fasterxml.jackson.annotation.JsonIgnore; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; import lombok.Data; import java.sql.Timestamp; import java.util.Objects; @Data -@JsonSerialize -@JsonDeserialize public class ExpiringCode { private String code; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java index dc4025e7a81..d49a19adac4 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java @@ -16,12 +16,10 @@ import tools.jackson.core.JsonParser; import tools.jackson.databind.DeserializationContext; -import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import tools.jackson.core.TokenStreamLocation; import tools.jackson.core.exc.StreamReadException; import tools.jackson.databind.ValueDeserializer; @@ -35,14 +33,14 @@ public class JsonDateDeserializer extends ValueDeserializer { @Override public Date deserialize(JsonParser parser, DeserializationContext context) { - return getDate(parser.getString(), parser.currentLocation()); + return getDate(parser.getString(), parser); } - public static Date getDate(String text, TokenStreamLocation loc) throws IOException { + public static Date getDate(String text, JsonParser parser) { try { return new SimpleDateFormat(DATE_FORMATTER).parse(text); } catch (ParseException e) { - throw new StreamReadException("Could not parse date:" + text, loc, e); + throw new StreamReadException(parser, "Could not parse date:" + text, e); } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java index c0e39e82b44..472fa805962 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Deserializer.java @@ -8,7 +8,6 @@ import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; import tools.jackson.core.exc.StreamReadException; -import java.io.IOException; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; @@ -83,9 +82,9 @@ public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) return accessToken; } - private Set parseScope(JsonParser jp) throws IOException { + private Set parseScope(JsonParser jp) { Set scope; - if (jp.getCurrentToken() == JsonToken.START_ARRAY) { + if (jp.currentToken() == JsonToken.START_ARRAY) { scope = new TreeSet<>(); while (jp.nextToken() != JsonToken.END_ARRAY) { scope.add(jp.getValueAsString()); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java index 8b94c56ea18..b68ecccff3e 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2Serializer.java @@ -57,7 +57,7 @@ public void serialize(OAuth2AccessToken token, JsonGenerator jgen, Serialization } Map additionalInformation = token.getAdditionalInformation(); for (Map.Entry entry : additionalInformation.entrySet()) { - jgen.writeObjectProperty(entry.getKey(), entry.getValue()); + jgen.writePOJOProperty(entry.getKey(), entry.getValue()); } jgen.writeEndObject(); } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java index e4b7ab6cabe..ca38f59d810 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/common/exceptions/OAuth2ExceptionJackson2Deserializer.java @@ -33,14 +33,14 @@ public OAuth2ExceptionJackson2Deserializer() { @Override public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) { - JsonToken t = jp.getCurrentToken(); + JsonToken t = jp.currentToken(); if (t == JsonToken.START_OBJECT) { t = jp.nextToken(); } Map errorParams = new HashMap<>(); for (; t == JsonToken.PROPERTY_NAME; t = jp.nextToken()) { // Must point to field name - String fieldName = jp.getCurrentName(); + String fieldName = jp.currentName(); // And then the value... t = jp.nextToken(); // Note: must handle null explicitly here; value deserializers won't diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java index de1fe1f89e2..4f5fb259314 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwk/JsonWebKeyDeserializer.java @@ -30,7 +30,7 @@ public class JsonWebKeyDeserializer extends ValueDeserializer { @Override public JsonWebKey deserialize(JsonParser p, DeserializationContext ctxt) { - JsonNode node = JsonUtils.readTree(p); + JsonNode node = p.readValueAsTree(); String kty = node.get(JWKParameterNames.KEY_TYPE).asString("Unknown"); if (Arrays.stream(JsonWebKey.KeyType.values()).noneMatch(knownKeyType -> knownKeyType.name().equals(kty))) { return null; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java index 47e9fd97576..7a0c496e8f3 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java @@ -29,7 +29,6 @@ import tools.jackson.databind.ValueDeserializer; import tools.jackson.databind.ValueSerializer; import jakarta.validation.constraints.NotNull; -import java.io.IOException; import java.util.Date; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; @@ -328,7 +327,7 @@ public void serialize(IdentityProvider value, JsonGenerator gen, SerializationCo gen.writeStringProperty(FIELD_TYPE, value.getType()); if (value.isSerializeConfigRaw()) { - gen.writeObjectProperty(FIELD_CONFIG, value.getConfig()); + gen.writePOJOProperty(FIELD_CONFIG, value.getConfig()); } else { gen.writeStringProperty(FIELD_CONFIG, JsonUtils.writeValueAsString(value.getConfig())); } @@ -345,7 +344,7 @@ public void serialize(IdentityProvider value, JsonGenerator gen, SerializationCo gen.writeEndObject(); } - public void writeDateField(String fieldName, Date value, JsonGenerator gen) throws IOException { + public void writeDateField(String fieldName, Date value, JsonGenerator gen) { if (value != null) { gen.writeNumberProperty(fieldName, value.getTime()); } else { @@ -359,7 +358,7 @@ public static class IdentityProviderDeserializer extends ValueDeserializer { diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java index f4272bd2cd1..b76b3c7f591 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java @@ -40,7 +40,7 @@ public ScimMeta(Date created, Date lastModified, int version) { this.version = version; } - @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + @JsonSerialize(using = JsonDateSerializer.class) public Date getCreated() { return created; } @@ -50,7 +50,7 @@ public void setCreated(Date created) { this.created = created; } - @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + @JsonSerialize(using = JsonDateSerializer.class) public Date getLastModified() { return lastModified; } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java index 4feb187e4fb..0cd82c61419 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java @@ -588,7 +588,8 @@ public void setSalt(String salt) { this.salt = salt; } - @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + @JsonSerialize(using = JsonDateSerializer.class) + @JsonInclude(JsonInclude.Include.NON_NULL) public Date getPasswordLastModified() { if (passwordLastModified != null) { return passwordLastModified; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java index 7ad79374d75..2726d835949 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java @@ -34,8 +34,8 @@ public class ScimUserJsonDeserializer extends ValueDeserializer { public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) { ScimUser user = new ScimUser(); while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() == JsonToken.PROPERTY_NAME) { - String fieldName = jp.getCurrentName(); + if (jp.currentToken() == JsonToken.PROPERTY_NAME) { + String fieldName = jp.currentName(); jp.nextToken(); if ("id".equalsIgnoreCase(fieldName)) { @@ -95,7 +95,7 @@ public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) { user.setSalt(jp.readValueAs(String.class)); } else if ("passwordLastModified".equalsIgnoreCase(fieldName)) { if (jp.getValueAsString() != null) { - user.setPasswordLastModified(JsonDateDeserializer.getDate(jp.getValueAsString(), jp.currentLocation())); + user.setPasswordLastModified(JsonDateDeserializer.getDate(jp.getValueAsString(), jp)); } } else if ("approvals".equalsIgnoreCase(fieldName)) { user.setApprovals(new HashSet<>(Arrays.asList(jp.readValueAs(Approval[].class)))); @@ -108,7 +108,7 @@ public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) { user.setPreviousLogonTime(jp.getValueAsLong()); } } else { - throw new UnrecognizedPropertyException("unrecognized field", jp.currentLocation(), + throw UnrecognizedPropertyException.from(jp, ScimUser.class, fieldName, Collections.emptySet()); } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java index 3b28e3c9da5..3c7d03adfa6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java @@ -45,8 +45,8 @@ public UaaAuthentication deserialize(JsonParser jp, DeserializationContext ctxt) String idpIdToken = null; Map> userAttributes = emptyMap(); while (jp.nextToken() != JsonToken.END_OBJECT) { - if (jp.getCurrentToken() == JsonToken.PROPERTY_NAME) { - String fieldName = jp.getCurrentName(); + if (jp.currentToken() == JsonToken.PROPERTY_NAME) { + String fieldName = jp.currentName(); jp.nextToken(); if (NULL_STRING.equals(jp.getString())) { //do nothing @@ -84,7 +84,7 @@ public UaaAuthentication deserialize(JsonParser jp, DeserializationContext ctxt) } } if (princpal == null) { - throw new DatabindException("Missing " + UaaPrincipal.class.getName()); + throw DatabindException.from(jp, "Missing " + UaaPrincipal.class.getName()); } UaaAuthentication uaaAuthentication = new UaaAuthentication(princpal, null, diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java index 471cecb9304..d35b199a4ff 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java @@ -22,19 +22,19 @@ public class UaaAuthenticationSerializer extends ValueSerializer Date: Wed, 29 Apr 2026 00:36:54 +0300 Subject: [PATCH 41/58] Fix Jackson 3 serialization for IdToken and HeaderParameters IdToken: Add @JsonCreator with explicit @JsonProperty on all constructor parameters. Jackson 3 reports conflicting property names when a field has @JsonProperty("cid") but the getter has @JsonProperty("client_id"). The explicit @JsonCreator annotation resolves the ambiguity. HeaderParameters: Remove @JsonCreator from the 3-param constructor. Jackson 3's ConstructorDetector was selecting it during convertValue() operations even when 'alg' was absent, causing "alg is required" errors. The no-arg constructor with field-level @JsonProperty handles deserialization correctly. --- .../uaa/oauth/jwt/HeaderParameters.java | 2 - .../identity/uaa/oauth/openid/IdToken.java | 53 ++++++++++--------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java index 48d6c3cf09d..dc6ef353eb8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/jwt/HeaderParameters.java @@ -1,6 +1,5 @@ package org.cloudfoundry.identity.uaa.oauth.jwt; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -53,7 +52,6 @@ public class HeaderParameters { } - @JsonCreator HeaderParameters(String alg, String kid, String jku) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/openid/IdToken.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/openid/IdToken.java index 4278fd98480..3280efbf490 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/openid/IdToken.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/openid/IdToken.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.oauth.openid; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -74,31 +75,33 @@ public class IdToken { public final String revSig; private Map tokenActor; - public IdToken(String sub, - List aud, - String iss, - Date exp, - Date iat, - Date authTime, - Set amr, - Set acr, - String azp, - String givenName, - String familyName, - Long previousLogonTime, - String phoneNumber, - Set roles, - Map> userAttributes, - Boolean emailVerified, - String nonce, - String email, - String clientId, - String grantType, - String userName, - String zid, - String origin, - String jti, - String revSig) { + @JsonCreator + public IdToken( + @JsonProperty("sub") String sub, + @JsonProperty("aud") List aud, + @JsonProperty("iss") String iss, + @JsonProperty("exp") Date exp, + @JsonProperty(IAT) Date iat, + @JsonProperty(AUTH_TIME) Date authTime, + @JsonProperty("amr") Set amr, + @JsonProperty(ACR) Set acr, + @JsonProperty("azp") String azp, + @JsonProperty(GIVEN_NAME) String givenName, + @JsonProperty(FAMILY_NAME) String familyName, + @JsonProperty(PREVIOUS_LOGON_TIME) Long previousLogonTime, + @JsonProperty(PHONE_NUMBER) String phoneNumber, + @JsonProperty("roles") Set roles, + @JsonProperty(USER_ATTRIBUTES) Map> userAttributes, + @JsonProperty(EMAIL_VERIFIED) Boolean emailVerified, + @JsonProperty("nonce") String nonce, + @JsonProperty("email") String email, + @JsonProperty(CID) String clientId, + @JsonProperty(GRANT_TYPE) String grantType, + @JsonProperty(USER_NAME) String userName, + @JsonProperty("zid") String zid, + @JsonProperty("origin") String origin, + @JsonProperty("jti") String jti, + @JsonProperty(REVOCATION_SIGNATURE) String revSig) { this.sub = sub; this.aud = aud; this.iss = iss; From 2da7f5d52ccac9de843d288cfee6e040a06be21b Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Wed, 29 Apr 2026 00:37:49 +0300 Subject: [PATCH 42/58] Update tests for Jackson 3 API and exception changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update exception class name assertions: com.fasterxml.jackson.core.JsonParseException → tools.jackson.core.exc.StreamReadException com.fasterxml.jackson.databind.exc.InvalidDefinitionException → tools.jackson.databind.exc.InvalidDefinitionException IllegalArgumentException → MismatchedInputException (convertValue) - JsonDateDeserializerTest: pass JsonParser instead of TokenStreamLocation - JsonDateSerializerTest: use JsonMapper.shared().createGenerator() instead of TokenStreamFactory (removed in Jackson 3) - JsonTranslation: enable WRITE_DATES_AS_TIMESTAMPS (no longer default) - OAuth2AccessTokenJackson2SerializerTests: DatabindException → IllegalArgumentException (thrown directly by serializer, not wrapped by Jackson 3) - SAML tests: use Jackson 2 ObjectMapper for Spring Security's Jackson2 modules (SecurityJackson2Modules requires Jackson 2 ObjectMapper) --- .../identity/uaa/util/JsonUtilsTest.java | 21 +++++++++---------- .../uaa/impl/JsonDateDeserializerTest.java | 12 ++++++----- .../uaa/impl/JsonDateSerializerTest.java | 13 +++++------- ...th2AccessTokenJackson2SerializerTests.java | 7 +++---- .../identity/uaa/test/JsonTranslation.java | 4 +++- ...nSaml5AuthenticationProviderUnitTests.java | 4 +--- ...earerGrantAuthenticationConverterTest.java | 4 +--- 7 files changed, 30 insertions(+), 35 deletions(-) diff --git a/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java b/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java index eefd0468d33..8dccc30d4d0 100644 --- a/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java +++ b/metrics-data/src/test/java/org/cloudfoundry/identity/uaa/util/JsonUtilsTest.java @@ -139,12 +139,11 @@ void cannotInstantiate() { } @Test - void throwsException_writeValueAsString() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.writeValueAsString(new Object()) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Object"); + assertThat(exception.getMessage()).startsWith("tools.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Object"); } @Test @@ -152,7 +151,7 @@ void throwsException_writeValueAsBytes() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.writeValueAsBytes(new Object()) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Object"); + assertThat(exception.getMessage()).startsWith("tools.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Object"); } @Test @@ -160,22 +159,22 @@ void throwsException_readValue() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValue("invalid json", String.class) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'invalid'"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unrecognized token 'invalid'"); exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValue("invalid json".getBytes(), String.class) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'invalid'"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unrecognized token 'invalid'"); exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValue("invalid json", new TypeReference() {}) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'invalid'"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unrecognized token 'invalid'"); exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValue("invalid json".getBytes(), new TypeReference() {}) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'invalid'"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unrecognized token 'invalid'"); } @Test @@ -183,7 +182,7 @@ void throwsException_readValueAsMap() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValueAsMap("invalid json") ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'invalid'"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unrecognized token 'invalid'"); } @Test @@ -191,7 +190,7 @@ void throwsException_convertValue() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.convertValue(Boolean.TRUE, Integer.class) ); - assertThat(exception.getMessage()).startsWith("java.lang.IllegalArgumentException: Cannot deserialize value of type `java.lang.Integer` from Boolean value"); + assertThat(exception.getMessage()).startsWith("tools.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.Integer` from Boolean value"); } @Test @@ -201,7 +200,7 @@ void throwsException_readTree() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readTree("invalid json") ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'invalid'"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unrecognized token 'invalid'"); } @Test @@ -211,7 +210,7 @@ void throwsException_readTreeWithParserArg() throws JacksonException { JsonUtils.JsonUtilException exception = assertThrows(JsonUtils.JsonUtilException.class, () -> JsonUtils.readValue("{'valid':'json'}", SerializerTestObject.class) ); - assertThat(exception.getMessage()).startsWith("com.fasterxml.jackson.core.JsonParseException: Unexpected character"); + assertThat(exception.getMessage()).startsWith("tools.jackson.core.exc.StreamReadException: Unexpected character"); } @Test diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java index 617333f9d58..75a911a8642 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializerTest.java @@ -15,9 +15,9 @@ package org.cloudfoundry.identity.uaa.impl; import org.junit.jupiter.api.Test; -import tools.jackson.core.TokenStreamLocation; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.json.JsonMapper; -import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -30,8 +30,9 @@ class JsonDateDeserializerTest { Exception exceptionOccured; @Test - void parsing() throws IOException, ParseException { - Date d = JsonDateDeserializer.getDate(testDateString, new TokenStreamLocation(null, 22, 0, 0)); + void parsing() throws ParseException { + JsonParser jp = JsonMapper.shared().createParser("\"" + testDateString + "\""); + Date d = JsonDateDeserializer.getDate(testDateString, jp); assertThat((long) d.getTime()).isEqualTo(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(testDateString).getTime()); } @@ -42,7 +43,8 @@ void parsingParallel() throws InterruptedException { threadArray[i] = new Thread(() -> { try { - Date d = JsonDateDeserializer.getDate(testDateString, new TokenStreamLocation(null, 22, 0, 0)); + JsonParser jp = JsonMapper.shared().createParser("\"" + testDateString + "\""); + Date d = JsonDateDeserializer.getDate(testDateString, jp); if (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(testDateString).getTime() != d.getTime()) { throw new Exception("Unexpected date"); } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java index 18ea1746010..210be7f07fc 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializerTest.java @@ -1,11 +1,10 @@ package org.cloudfoundry.identity.uaa.impl; import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.json.JsonMapper; import org.junit.jupiter.api.Test; -import tools.jackson.core.TokenStreamFactory; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; @@ -16,10 +15,10 @@ class JsonDateSerializerTest { Exception exceptionOccurred; @Test - void formatting() throws IOException { + void formatting() { Date now = new Date(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - JsonGenerator gen = new TokenStreamFactory().createGenerator(bos); + JsonGenerator gen = JsonMapper.shared().createGenerator(bos); new JsonDateSerializer().serialize(now, gen, null); gen.close(); assertThat(bos).hasToString("\"%s\"".formatted(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(now))); @@ -34,7 +33,7 @@ void formattingParallel() throws InterruptedException { try { Date now = new Date(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - JsonGenerator gen = new TokenStreamFactory().createGenerator(bos); + JsonGenerator gen = JsonMapper.shared().createGenerator(bos); new JsonDateSerializer().serialize(now, gen, null); gen.close(); if (!"\"%s\"".formatted(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format(now)) @@ -47,9 +46,7 @@ void formattingParallel() throws InterruptedException { } }); } - for ( - - int i = 0; i < 1000; i++) { + for (int i = 0; i < 1000; i++) { threadArray[i].start(); } for (int i = 0; i < 1000; i++) { diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java index a06ee674cd9..34f8f93fd50 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/oauth/common/OAuth2AccessTokenJackson2SerializerTests.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import tools.jackson.databind.DatabindException; import java.io.IOException; @@ -62,11 +61,11 @@ void writeValueAsStringWithNullScope() { accessToken.getScope().add(null); } catch (NullPointerException e) { // short circuit NPE from Java 7 (which is correct but only relevant for this test) - throw new DatabindException("Scopes cannot be null or empty. Got [null]"); + throw new IllegalArgumentException("Scopes cannot be null or empty. Got [null]"); } mapper.writeValueAsString(accessToken); }) - .isInstanceOf(DatabindException.class) + .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Scopes cannot be null or empty. Got [null]"); } @@ -75,7 +74,7 @@ void writeValueAsStringWithEmptyStringScope() { accessToken.getScope().clear(); accessToken.getScope().add(""); assertThatThrownBy(() -> mapper.writeValueAsString(accessToken)) - .isInstanceOf(DatabindException.class) + .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Scopes cannot be null or empty. Got []"); } diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java b/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java index 6af526129cd..2d99dcc0510 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/test/JsonTranslation.java @@ -47,7 +47,9 @@ protected void setUp( this.withAllNullFields = withAllNullFields; this.jsonFileName = subjectClass.getSimpleName() + ".json"; - this.objectMapper = new JsonMapper(); + this.objectMapper = JsonMapper.builder() + .enable(tools.jackson.databind.cfg.DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); } protected ObjectMapper getObjectMapper() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java index d7bc30fa277..951f2989d33 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml5AuthenticationProviderUnitTests.java @@ -1,7 +1,5 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; import net.shibboleth.shared.xml.SerializeSupport; import org.cloudfoundry.identity.uaa.provider.saml.OpenSaml5AuthenticationProvider.ResponseToken; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -502,8 +500,8 @@ void authenticateWhenAssertionContainsAttributesThenItSucceeds() { // gh-11785 @Test void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { - ObjectMapper mapper = new JsonMapper(); ClassLoader loader = getClass().getClassLoader(); + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); mapper.registerModules(SecurityJackson2Modules.getModules(loader)); Response response = response(); Assertion assertion = assertion(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java index b9194720e1d..85a7cb18c99 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2BearerGrantAuthenticationConverterTest.java @@ -1,7 +1,5 @@ package org.cloudfoundry.identity.uaa.provider.saml; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.json.JsonMapper; import net.shibboleth.shared.xml.SerializeSupport; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; import org.cloudfoundry.identity.uaa.impl.config.RestTemplateConfig; @@ -260,8 +258,8 @@ void authenticateWhenAssertionContainsAttributesThenItSucceeds() { // gh-11785 @Test void deserializeWhenAssertionContainsAttributesThenWorks() throws Exception { - ObjectMapper mapper = new JsonMapper(); ClassLoader loader = getClass().getClassLoader(); + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); mapper.registerModules(SecurityJackson2Modules.getModules(loader)); Assertion assertion = assertion(); List attributes = TestOpenSamlObjects.attributeStatements(); From eb9ae8374a8e2905e4482f5f84a84b0cc356e24d Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 3 May 2026 13:23:06 +0300 Subject: [PATCH 43/58] Fix Jackson 3 FAIL_ON_NULL_FOR_PRIMITIVES in integration test RestTemplates Move RestTemplate Jackson configuration into IntegrationTestUtils. Adds createConfiguredRestTemplate() and configureRestTemplate() mirroring the production JacksonMapperCustomizer pattern (find first JacksonJsonHttpMessageConverter and replace it). Applies to all test RestTemplate and OAuth2RestTemplate instances. Consolidates the duplicate getClientCredentialsToken(ServerRunningExtension,...) overload to delegate to the URL-based one. # Conflicts: # uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java # uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java --- .../identity/uaa/ServerRunningExtension.java | 19 +++++++++++++++++++ .../ClientAdminEndpointsIntegrationTests.java | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java b/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java index 2bc13e14163..6bcce4c5b00 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java @@ -34,9 +34,15 @@ import org.springframework.http.client.support.HttpAccessor; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.cfg.ConstructorDetector; +import tools.jackson.databind.json.JsonMapper; import org.springframework.web.util.UriUtils; import java.io.IOException; @@ -44,6 +50,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.List; import java.util.Map; import static java.nio.charset.StandardCharsets.UTF_8; @@ -300,6 +307,18 @@ public void setRestTemplate(RestOperations restTemplate) { public RestTemplate createRestTemplate() { RestTemplate newClient = new RestTemplate(); + JsonMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .constructorDetector(ConstructorDetector.DEFAULT.withAllowImplicitWithDefaultConstructor(false)) + .build(); + List> converters = newClient.getMessageConverters(); + for (int i = 0; i < converters.size(); i++) { + if (converters.get(i) instanceof JacksonJsonHttpMessageConverter) { + converters.set(i, new JacksonJsonHttpMessageConverter(mapper)); + break; + } + } newClient.setRequestFactory(new StatelessRequestFactory()); newClient.setErrorHandler(new ResponseErrorHandler() { // Pass errors through in response entity for status code analysis diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java index 05e3e69792a..41bd3ad6992 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java @@ -406,12 +406,12 @@ void nonImplicitGrantClientWithSecret() { Collections.singletonList("bar"))); } clients[clients.length - 1].setClientSecret(null); - ResponseEntity result = + ResponseEntity result = serverRunning.getRestTemplate().exchange( serverRunning.getUrl("/oauth/clients/tx"), HttpMethod.POST, new HttpEntity<>(clients, headers), - UaaException.class); + UaaClientDetails[].class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); for (String id : ids) { ClientDetails client = getClient(id); From fa17bc6dd1c7b41e80fb86d802195e994921db42 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 3 May 2026 17:27:16 +0300 Subject: [PATCH 44/58] Fix client authentication with empty secret under Spring Security 7 Spring Security 7 AbstractValidatingPasswordEncoder.matches() returns false when rawPassword.length() == 0, breaking clients with no secret (e.g. CF CLI). Wrap the PasswordEncoder in EmptyPasswordAwareEncoder which handles the empty credential case by calling BCrypt.checkpw directly (bypassing the guard) for BCrypt hashes, or comparing the extracted value for prefix-encoded passwords. --- .../ClientDetailsAuthenticationProvider.java | 17 +---- .../EmptyPasswordAwareEncoder.java | 70 +++++++++++++++++++ .../EmptyPasswordAwareEncoderTest.java | 46 ++++++++++++ 3 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java index 2814bc223cd..6b59a9aa40a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientDetailsAuthenticationProvider.java @@ -53,7 +53,7 @@ public class ClientDetailsAuthenticationProvider extends DaoAuthenticationProvid public ClientDetailsAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder encoder, JwtClientAuthentication jwtClientAuthentication) { super(userDetailsService); - setPasswordEncoder(encoder); + setPasswordEncoder(new EmptyPasswordAwareEncoder(encoder)); this.jwtClientAuthentication = jwtClientAuthentication; } @@ -93,20 +93,7 @@ protected void additionalAuthenticationChecks(UserDetails userDetails, UsernameP error = new BadCredentialsException("Missing credentials"); break; } - // Spring Security 7 rejects empty passwords before validation. - // Handle empty credentials by bypassing parent's empty check - if (ObjectUtils.isEmpty(authentication.getCredentials())) { - String presentedPassword = authentication.getCredentials() == null ? "" : authentication.getCredentials().toString(); - String storedPassword = uaaClient.getPassword(); - // Handle {noop} encoded empty password: "{noop}" means empty password with noop encoding - if ("{noop}".equals(storedPassword) && presentedPassword.isEmpty()) { - error = null; - } else if (!getPasswordEncoder().matches(presentedPassword, storedPassword)) { - throw new BadCredentialsException("Bad credentials"); - } - } else { - super.additionalAuthenticationChecks(uaaClient, authentication); - } + super.additionalAuthenticationChecks(uaaClient, authentication); error = null; break; } catch (AuthenticationException e) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java new file mode 100644 index 00000000000..2777856bf5f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + * Wraps a {@link PasswordEncoder} to allow empty raw passwords. + * Spring Security 7 {@code AbstractValidatingPasswordEncoder} returns {@code false} from {@code matches()} + * when {@code rawPassword.length() == 0}. UAA legitimately supports clients with no secret (e.g. CF CLI), + * whose stored hash is the encoding of an empty string. + */ +class EmptyPasswordAwareEncoder implements PasswordEncoder { + + private final PasswordEncoder delegate; + + EmptyPasswordAwareEncoder(PasswordEncoder delegate) { + this.delegate = delegate; + } + + @Override + public String encode(CharSequence rawPassword) { + return delegate.encode(rawPassword); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + if (rawPassword != null && rawPassword.isEmpty()) { + return emptyPasswordMatchesStoredHash(encodedPassword); + } + + return delegate.matches(rawPassword, encodedPassword); + } + + private boolean emptyPasswordMatchesStoredHash(String encodedPassword) { + if (encodedPassword == null) { + return false; + } + + String extracted = extractPasswordValue(encodedPassword); + + if (isBcryptHash(extracted)) { + return bcryptMatchesEmpty(extracted); + } + + return extracted.isEmpty(); + } + + private String extractPasswordValue(String encodedPassword) { + int start = encodedPassword.indexOf('{'); + int end = encodedPassword.indexOf('}'); + + if (start == 0 && end > 0) { + return encodedPassword.substring(end + 1); + } + + return encodedPassword; + } + + private boolean isBcryptHash(String value) { + return value.startsWith("$2a$") || value.startsWith("$2b$") || value.startsWith("$2y$"); + } + + private boolean bcryptMatchesEmpty(String hash) { + try { + return BCrypt.checkpw("", hash); + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java new file mode 100644 index 00000000000..045b93ce802 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.identity.uaa.authentication; + +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.assertj.core.api.Assertions.assertThat; + +class EmptyPasswordAwareEncoderTest { + + private final PasswordEncoder bcrypt = new BCryptPasswordEncoder(); + private final EmptyPasswordAwareEncoder encoder = new EmptyPasswordAwareEncoder(bcrypt); + + @Test + void emptyRawPassword_matchesBcryptHashOfEmptyString() { + assertThat(encoder.matches("", bcrypt.encode(""))).isTrue(); + } + + @Test + void emptyRawPassword_matchesNoopPrefixWithEmptyValue() { + assertThat(encoder.matches("", "{noop}")).isTrue(); + } + + @Test + void emptyRawPassword_doesNotMatchBcryptHashOfNonEmptyString() { + assertThat(encoder.matches("", bcrypt.encode("notempty"))).isFalse(); + } + + @Test + void emptyRawPassword_doesNotMatchNullStoredPassword() { + assertThat(encoder.matches("", null)).isFalse(); + } + + @Test + void nonEmptyRawPassword_delegatesToWrappedEncoder() { + String encoded = bcrypt.encode("secret"); + assertThat(encoder.matches("secret", encoded)).isTrue(); + assertThat(encoder.matches("wrong", encoded)).isFalse(); + } + + @Test + void encode_delegatesToWrappedEncoder() { + String encoded = encoder.encode("secret"); + assertThat(bcrypt.matches("secret", encoded)).isTrue(); + } +} From c08db18532bd1cb95ac31f905e2c0430c755466d Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 3 May 2026 19:00:07 +0300 Subject: [PATCH 45/58] Fix integration tests for Spring Boot 4 / Jackson 3 / Spring Framework 7 API changes - Disable FAIL_ON_TRAILING_TOKENS in test RestTemplates (Jackson 3 default change) - Replace UNPROCESSABLE_ENTITY with UNPROCESSABLE_CONTENT (Spring Framework 7 rename) - Update expected Jackson error message from 'unrecognized field' to 'unrecognized property' # Conflicts: # uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java # Conflicts: # server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunningExtension.java --- .../uaa/integration/IdentityZoneEndpointsIntegrationTests.java | 2 +- .../uaa/integration/PasswordChangeEndpointIntegrationTests.java | 2 +- .../uaa/integration/ScimUserEndpointsIntegrationTests.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java index 66ef632ca48..29b0e9c2bcc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java @@ -180,7 +180,7 @@ void updateZoneAllowedGroups() { new ParameterizedTypeReference<>() { }, id); - assertThat(response.getStatusCode()).as(response.getBody()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY); + assertThat(response.getStatusCode()).as(response.getBody()).isEqualTo(HttpStatus.UNPROCESSABLE_CONTENT); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java index c8955221341..9ffb72fd948 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java @@ -134,7 +134,7 @@ void changePasswordSameAsOldFails() { .exchange(serverRunning.getUrl(userEndpoint) + "/{id}/password", HttpMethod.PUT, new HttpEntity<>(change, headers), Void.class, joe.getId()); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY); + assertThat(result.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_CONTENT); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java index 9b74a55971b..4dcfc6ca85e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java @@ -277,7 +277,7 @@ void updateUserWithBadAttributeFails() { ResponseEntity response = client.exchange(serverRunning.getUrl(userEndpoint) + "/{id}", HttpMethod.PUT, new HttpEntity(map, headers), Map.class, joe.getId()); Map joe1 = response.getBody(); - assertThat(((String) joe1.get("message")).toLowerCase()).as("Wrong message: " + joe1).contains("unrecognized field"); + assertThat(((String) joe1.get("message")).toLowerCase()).as("Wrong message: " + joe1).contains("unrecognized property"); } @SuppressWarnings({"rawtypes", "unchecked"}) From a848c50c65e900c7cd9c18802e9dbb2c065447c9 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 3 May 2026 19:00:38 +0300 Subject: [PATCH 46/58] Handle Tomcat 11 InvalidParameterException in UaaLoggingFilter --- .../web/SecurityFilterChainPostProcessor.java | 8 +++++++ ...SecurityFilterChainPostProcessorTests.java | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java index 50f795fc454..eeb017c524d 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java @@ -15,6 +15,7 @@ package org.cloudfoundry.identity.uaa.security.web; import jakarta.servlet.RequestDispatcher; +import org.apache.tomcat.util.http.InvalidParameterException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -267,6 +268,13 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) request.getRequestDispatcher("/rejected").forward(request, response); return; } + if (x instanceof InvalidParameterException) { + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"error\":\"parameter_parsing_error\",\"error_description\":\"One of the parameters was incorrectly encoded\"}"); + response.flushBuffer(); + return; + } ReasonPhrase reasonPhrase = getErrorMap().get(x.getClass()); if (null == reasonPhrase) { for (Class clazz : getErrorMap().keySet()) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java index 72238f9dd49..71ed3c8358a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java @@ -1,8 +1,11 @@ package org.cloudfoundry.identity.uaa.security.web; +import org.apache.tomcat.util.http.InvalidParameterException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.web.SecurityFilterChain; import jakarta.servlet.Filter; @@ -146,4 +149,22 @@ public static class AfterFilter extends TestFilter1 { public static class BeforeFilter extends TestFilter1 { } + + @Test + void uaaLoggingFilter_invalidParameterException_returns400JsonAndDoesNotPropagate() throws Exception { + processor.postProcessAfterInitialization(fc, ""); + Filter loggingFilter = fc.getFilters().getFirst(); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/oauth/token"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = (req, res) -> { + throw new InvalidParameterException("bad encoding"); + }; + + loggingFilter.doFilter(request, response, chain); + + assertThat(response.getStatus()).isEqualTo(400); + assertThat(response.getContentType()).contains("application/json"); + assertThat(response.getContentAsString()).contains("parameter_parsing_error"); + } } \ No newline at end of file From 4137c7dfda0d3f9995933a1a4e263626a89a1ad0 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Sun, 3 May 2026 20:34:43 +0300 Subject: [PATCH 47/58] Fix ClientSecretPolicy Jackson 3 @JsonIgnore on expireSecretInMonths --- .../identity/uaa/zone/ClientSecretPolicy.java | 4 +++- .../uaa/config/IdentityZoneConfigurationTests.java | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/ClientSecretPolicy.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/ClientSecretPolicy.java index 419d7d40b46..fc6c0c47103 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/ClientSecretPolicy.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/ClientSecretPolicy.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.cloudfoundry.identity.uaa.authentication.GenericPasswordPolicy; -@JsonIgnoreProperties(ignoreUnknown = true) +@JsonIgnoreProperties(ignoreUnknown = true, value = {"expireSecretInMonths"}) public class ClientSecretPolicy extends GenericPasswordPolicy { @Override @@ -68,10 +68,12 @@ public ClientSecretPolicy(int minLength, this.setExpireSecretInMonths(expireSecretInMonths); } + @JsonIgnore public int getExpireSecretInMonths() { return expireSecretInMonths; } + @JsonIgnore public ClientSecretPolicy setExpireSecretInMonths(int expireSecretInMonths) { this.expireSecretInMonths = expireSecretInMonths; return this; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index a1d00f6f18d..13710f028db 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java @@ -87,6 +87,16 @@ void deserializeClientSecretPolicyJSON_withUnknownProperties_doesNotFail() { JsonUtils.readValue(config, ClientSecretPolicy.class); } + @Test + void clientSecretPolicy_expireSecretInMonths_isIgnoredInJson() { + ClientSecretPolicy policy = new ClientSecretPolicy(5, 12, 1, 1, 1, 1, 6); + String json = JsonUtils.writeValueAsString(policy); + assertThat(json).doesNotContain("expireSecretInMonths"); + + ClientSecretPolicy back = JsonUtils.readValue(json, ClientSecretPolicy.class); + assertThat(back.getExpireSecretInMonths()).isEqualTo(-1); + } + @Test void deserializeLinksJSON_withUnknownProperties_doesNotFail() { String config = "{ \"unknown-property\": \"unknown-value\"}"; From 29a99796960e3f8c514812fa42bd900b986b6c6b Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Mon, 4 May 2026 18:06:30 +0300 Subject: [PATCH 48/58] reformat --- .../identity/uaa/util/JsonUtils.java | 3 +- .../impl/config/JacksonMapperCustomizer.java | 3 +- .../invitations/EmailInvitationsService.java | 2 +- .../feature/DefaultIntegrationTestConfig.java | 3 +- .../util/IntegrationTestUtils.java | 88 +++++++++++-------- 5 files changed, 55 insertions(+), 44 deletions(-) diff --git a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java index 4e70244e1c9..ea2ed7e69f9 100644 --- a/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java +++ b/metrics-data/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java @@ -37,8 +37,7 @@ public class JsonUtils { .enable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) .disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - .constructorDetector(ConstructorDetector.DEFAULT - .withAllowImplicitWithDefaultConstructor(false)) + .constructorDetector(ConstructorDetector.DEFAULT.withAllowImplicitWithDefaultConstructor(false)) .build(); private JsonUtils() { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java index 3a7bf851820..26c191f80cd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/JacksonMapperCustomizer.java @@ -26,8 +26,7 @@ public JsonMapper jsonMapper() { .enable(SerializationFeature.FAIL_ON_EMPTY_BEANS) .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) .disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - .constructorDetector(ConstructorDetector.DEFAULT - .withAllowImplicitWithDefaultConstructor(false)) + .constructorDetector(ConstructorDetector.DEFAULT.withAllowImplicitWithDefaultConstructor(false)) .build(); } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java index c88899e518d..5cf72fe9a6c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.invitations; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java index eb3a9701f5c..59c890bd8a5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/DefaultIntegrationTestConfig.java @@ -15,6 +15,7 @@ import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.ServerSetup; +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.oauth.client.test.TestAccounts; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.test.UaaWebDriver; @@ -125,7 +126,7 @@ public GreenMail greenMail(@Value("${smtp.port}") int port) { @Bean public RestTemplate restTemplate() { - return new RestTemplate(); + return IntegrationTestUtils.createConfiguredRestTemplate(); } @Bean diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index db7657fa882..51a7aad47fe 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -1,6 +1,10 @@ package org.cloudfoundry.identity.uaa.integration.util; -import com.fasterxml.jackson.core.type.TypeReference; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.cfg.ConstructorDetector; +import tools.jackson.databind.json.JsonMapper; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.GreenMailUtil; import jakarta.mail.internet.MimeMessage; @@ -58,7 +62,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -263,8 +269,30 @@ public static ClientCredentialsResourceDetails getClientCredentialsResource(Stri return resource; } + public static RestTemplate createConfiguredRestTemplate() { + RestTemplate template = new RestTemplate(); + configureRestTemplate(template); + return template; + } + + public static void configureRestTemplate(RestTemplate template) { + JsonMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .constructorDetector(ConstructorDetector.DEFAULT.withAllowImplicitWithDefaultConstructor(false)) + .build(); + List> converters = template.getMessageConverters(); + for (int i = 0; i < converters.size(); i++) { + if (converters.get(i) instanceof JacksonJsonHttpMessageConverter) { + converters.set(i, new JacksonJsonHttpMessageConverter(mapper)); + break; + } + } + } + public static RestTemplate getClientCredentialsTemplate(ClientCredentialsResourceDetails details) { RestTemplate client = new OAuth2RestTemplate(details); + configureRestTemplate(client); client.setRequestFactory(new StatelessRequestFactory()); client.setErrorHandler(new OAuth2ErrorHandler(details) { // Pass errors through in response entity for status code analysis @@ -315,7 +343,7 @@ public static ScimUser createUserWithPhone(RestTemplate client, } public static ScimUser createUser(String token, String url, ScimUser user, String zoneSwitchId) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -338,7 +366,7 @@ public static ScimUser createUser(String token, String url, ScimUser user, Strin } public static void updateUser(String token, String url, ScimUser user) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -364,7 +392,7 @@ public static ScimUser getUser(String token, String url, String origin, String u } public static ScimUser getUserByZone(String token, String url, String subdomain, String username) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -393,7 +421,7 @@ public static ScimUser getUserByZone(String token, String url, String subdomain, } public static ScimUser getUser(String token, String url, String userId) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -416,7 +444,7 @@ public static String getUserId(String token, String url, String origin, String u } public static String getUserIdByField(String token, String url, String origin, String field, String fieldValue) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -450,7 +478,7 @@ public static String getUsernameById(String token, String url, String userId) { public static void deleteUser(String zoneAdminToken, String url, String userId) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + zoneAdminToken); @@ -514,7 +542,7 @@ public static ScimGroup getGroup(String token, String zoneId, String url, String displayName) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -569,7 +597,7 @@ private static ScimGroup updateGroup(String token, String zoneId, String url, ScimGroup group) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -609,7 +637,7 @@ public static ScimGroupExternalMember mapExternalGroup(String token, String url, ScimGroupExternalMember scimGroup) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -636,7 +664,7 @@ public static void deleteGroup(String token, String url, String groupId ) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); template.setErrorHandler(fiveHundredErrorHandler); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Authorization", "bearer " + token); @@ -713,7 +741,7 @@ public static void addMemberToGroup(RestTemplate client, public static UaaClientDetails getClient(String token, String url, String clientId) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -752,7 +780,7 @@ public static UaaClientDetails createClientAsZoneAdmin(String zoneAdminToken, String zoneId, UaaClientDetails client) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + zoneAdminToken); @@ -782,7 +810,7 @@ public static UaaClientDetails createOrUpdateClient(String adminToken, String switchToZoneId, UaaClientDetails client) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); template.setErrorHandler(new DefaultResponseErrorHandler() { @Override protected boolean hasError(HttpStatusCode statusCode) { @@ -826,7 +854,7 @@ public static void updateClient(String url, String token, UaaClientDetails client) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + token); @@ -865,7 +893,7 @@ public static IdentityProvider get private static List> getProviders(String zoneAdminToken, String url, String zoneId) { - RestTemplate client = new RestTemplate(); + RestTemplate client = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + zoneAdminToken); @@ -893,7 +921,7 @@ public static void deleteProvider(String zoneAdminToken, if (provider == null) { return; } - RestTemplate client = new RestTemplate(); + RestTemplate client = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Authorization", "bearer " + zoneAdminToken); headers.add(IdentityZoneSwitchingFilter.HEADER, zoneId); @@ -1060,7 +1088,7 @@ public static IdentityProvider String url, IdentityProvider provider) { - RestTemplate client = new RestTemplate(); + RestTemplate client = createConfiguredRestTemplate(); MultiValueMap headers = new LinkedMultiValueMap<>(); headers.add("Accept", APPLICATION_JSON_VALUE); headers.add("Authorization", "bearer " + accessToken); @@ -1102,7 +1130,7 @@ public static IdentityProvider public static String getClientCredentialsToken(String baseUrl, String clientId, String clientSecret) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); template.setRequestFactory(new StatelessRequestFactory()); MultiValueMap formData = new LinkedMultiValueMap<>(); formData.add("grant_type", "client_credentials"); @@ -1144,7 +1172,7 @@ public static Map getPasswordToken(String baseUrl, String password, String scopes, String loginHint) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); template.getMessageConverters().addFirst(new StringHttpMessageConverter(StandardCharsets.UTF_8)); template.setRequestFactory(new StatelessRequestFactory()); MultiValueMap formData = new LinkedMultiValueMap<>(); @@ -1181,7 +1209,7 @@ public static Map getJwtBearerToken(String baseUrl, String assertion, String loginHint, String scopes) { - RestTemplate template = new RestTemplate(); + RestTemplate template = createConfiguredRestTemplate(); template.getMessageConverters().addFirst(new StringHttpMessageConverter(StandardCharsets.UTF_8)); template.setRequestFactory(new StatelessRequestFactory()); MultiValueMap formData = new LinkedMultiValueMap<>(); @@ -1214,23 +1242,7 @@ public static Map getJwtBearerToken(String baseUrl, public static String getClientCredentialsToken(ServerRunningExtension serverRunning, String clientId, String clientSecret) { - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("grant_type", "client_credentials"); - formData.add("client_id", clientId); - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - headers.set("Authorization", - "Basic " + new String(BASE_64_ENCODER.encode("%s:%s".formatted(clientId, clientSecret).getBytes()))); - - @SuppressWarnings("rawtypes") - ResponseEntity response = serverRunning.postForMap("/oauth/token", formData, headers); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - - final Map responseBody = response.getBody(); - assertThat(responseBody).isNotNull(); - @SuppressWarnings("unchecked") - OAuth2AccessToken accessToken = DefaultOAuth2AccessToken.valueOf(responseBody); - return accessToken.getValue(); + return getClientCredentialsToken(serverRunning.getBaseUrl(), clientId, clientSecret); } public static String getAccessTokenByAuthCode(ServerRunningExtension serverRunning, From c079d94160b43e888f35462f5cee52015a1b8177 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 12 May 2026 23:43:02 +0300 Subject: [PATCH 49/58] fix flaky tests --- .../identity/uaa/integration/feature/SamlLoginIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 8ab632744f9..205549ac4ab 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -158,6 +158,8 @@ void clearWebDriverOfCookies() { LogoutDoEndpoint.logout(webDriver, baseUrl.replace("localhost", domain)); new Page(webDriver).clearCookies(); } + webDriver.get(samlServerConfig.getSamlServerUrl() + "/index.php"); + webDriver.manage().deleteAllCookies(); SamlLogoutAuthSourceEndpoint.assertThatLogoutAuthSource_goesToSamlWelcomePage(webDriver, samlServerConfig); } From 86a201bbefb1ed2cfd5b02a0dc7fe44c88acd099 Mon Sep 17 00:00:00 2001 From: Duane May Date: Mon, 18 May 2026 13:25:27 -0400 Subject: [PATCH 50/58] Fix libraries missed in rebase --- gradle/libs.versions.toml | 3 ++- server/build.gradle | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab031a20c9f..9017bac669f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ selenium = "4.44.0" sonarqube = "7.3.0.8198" springBoot = "4.0.0" springDependencyManagement = "1.1.7" -springDocOpenapi = "2.8.17" +springDocOpenapi = "3.0.2" springRetry = "2.0.12" statsdClient = "3.1.0" unboundIdScimSdk = "2.0.0" @@ -56,6 +56,7 @@ velocity = { module = "org.apache.velocity:velocity-engine-core", version.ref = xmlSecurity = { module = "org.apache.santuario:xmlsec", version.ref = "xmlSecurity" } # AspectJ +aspectJRt = { module = "org.aspectj:aspectjrt" } aspectJWeaver = { module = "org.aspectj:aspectjweaver" } # Awaitility diff --git a/server/build.gradle b/server/build.gradle index 3cb999bcfee..0b8de4e3660 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -40,7 +40,7 @@ dependencies { implementation(libs.guava) // OpenAPI documentation - implementation(libs.springdocOpenapi) + implementation(libs.springDocOpenapi) implementation(libs.aspectJRt) implementation(libs.aspectJWeaver) From 1678d696af97bad5c5414768b5b8437c5e163974 Mon Sep 17 00:00:00 2001 From: Duane May Date: Mon, 18 May 2026 15:09:49 -0400 Subject: [PATCH 51/58] Bump springBoot from 4.0.0 to 4.0.6 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9017bac669f..2b42b08f350 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ orgJson = "20251224" passay = "2.0.0" selenium = "4.44.0" sonarqube = "7.3.0.8198" -springBoot = "4.0.0" +springBoot = "4.0.6" springDependencyManagement = "1.1.7" springDocOpenapi = "3.0.2" springRetry = "2.0.12" From dc9623c1ab6e85030356432621a5bf8bfac9eb85 Mon Sep 17 00:00:00 2001 From: Duane May Date: Mon, 18 May 2026 16:19:02 -0400 Subject: [PATCH 52/58] Increase heap on unit tests --- build.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 957ba087867..62062237652 100644 --- a/build.gradle +++ b/build.gradle @@ -128,8 +128,11 @@ subprojects { // gradle might stop the test run due to the failFast but still concludes with BUILD SUCCESSFUL (if the retry is successful) failFast = false useJUnitPlatform() - // Reduced from 1024m to 640m - unit tests don't need as much as integration tests - jvmArgs += ["-Xmx640m", + // Increased to 1536m for Spring Boot 4 compatibility + jvmArgs += ["-Xmx1536m", + "-Xms512m", + "-XX:MaxMetaspaceSize=512m", + "-XX:+UseG1GC", "-XX:+StartAttachListener", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=/var/log/uaa-tests.hprof" From aabc90277260d9595a27659f85fab6e99b3d47d9 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 19 May 2026 14:29:02 +0300 Subject: [PATCH 53/58] review: revert 4b4bdd529 and change config --- .../uaa/zone/beans/IdentityZoneSecurityConfiguration.java | 2 ++ .../mock/providers/IdentityProviderEndpointsMockMvcTests.java | 2 +- .../IdentityProviderEndpointsMockMvcZonePathTests.java | 2 +- .../uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java | 4 ++-- .../mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/beans/IdentityZoneSecurityConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/beans/IdentityZoneSecurityConfiguration.java index 8fac364c212..7945723ecc2 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/beans/IdentityZoneSecurityConfiguration.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/beans/IdentityZoneSecurityConfiguration.java @@ -16,6 +16,7 @@ import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.authentication.AuthenticationManagerBeanDefinitionParser; import org.springframework.security.config.http.SessionCreationPolicy; @@ -125,6 +126,7 @@ public SecurityFilterChain identityZones( }) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(oauth2ResourceFilter, AbstractPreAuthenticatedProcessingFilter.class) + .anonymous(AnonymousConfigurer::disable) .csrf(CsrfConfigurer::disable) .exceptionHandling(exception -> { exception.authenticationEntryPoint(oauthAuthenticationEntryPoint); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java index e0fbb4bbec7..f56e0344adb 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcTests.java @@ -213,7 +213,7 @@ void create_and_delete_saml_provider() throws Exception { //no access token mockMvc.perform( delete("/identity-providers/{id}", created.getId()) - ).andExpect(status().isForbidden()); + ).andExpect(status().isUnauthorized()); mockMvc.perform( delete("/identity-providers/{id}", created.getId()) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java index 2c5ac30c598..3c1c7175880 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/providers/IdentityProviderEndpointsMockMvcZonePathTests.java @@ -227,7 +227,7 @@ void create_and_delete_saml_provider() throws Exception { //no access token mockMvc.perform( delete("/identity-providers/{id}", created.getId()) - ).andExpect(status().isForbidden()); + ).andExpect(status().isUnauthorized()); mockMvc.perform( delete("/identity-providers/{id}", created.getId()) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 78806b91ced..aaa79ea28a7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -321,7 +321,7 @@ void delete_zone_as_with_uaa_admin() throws Exception { @MethodSource("parameters") void readWithoutTokenShouldFail(String url) throws Exception { mockMvc.perform(get(url)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @ParameterizedTest @@ -1637,7 +1637,7 @@ void delete_zone_cleans_db() throws Exception { .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(client))) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); MvcResult result = mockMvc.perform( post("/identity-zones/" + zone.getId() + "/clients") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java index 01839f5a84d..1483de915ee 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcZonePathTests.java @@ -329,7 +329,7 @@ void delete_zone_as_with_uaa_admin() throws Exception { @MethodSource("parameters") void readWithoutTokenShouldFail(String url) throws Exception { mockMvc.perform(get(url)) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); } @ParameterizedTest @@ -1645,7 +1645,7 @@ void delete_zone_cleans_db() throws Exception { .contentType(APPLICATION_JSON) .accept(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(client))) - .andExpect(status().isForbidden()); + .andExpect(status().isUnauthorized()); MvcResult result = mockMvc.perform( post("/identity-zones/" + zone.getId() + "/clients") From 2d20c2757bd485e5f52bbf975a24e24d5a6d2fc2 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 19 May 2026 14:37:08 +0300 Subject: [PATCH 54/58] review: tighten EmptyPasswordAwareEncoder allow-list The previous fall-through accepted any stored value whose contents after an optional {prefix} were empty, including a raw "" database value or unsupported prefixes like {plaintext} or a malformed {bcrypt} with no hash. Restrict the empty-password match to a valid bcrypt hash of the empty string and the explicit {noop} legacy marker. --- .../EmptyPasswordAwareEncoder.java | 9 ++++---- .../EmptyPasswordAwareEncoderTest.java | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java index 2777856bf5f..49ad6871855 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java @@ -36,13 +36,12 @@ private boolean emptyPasswordMatchesStoredHash(String encodedPassword) { return false; } - String extracted = extractPasswordValue(encodedPassword); - - if (isBcryptHash(extracted)) { - return bcryptMatchesEmpty(extracted); + if ("{noop}".equals(encodedPassword)) { + return true; } - return extracted.isEmpty(); + String extracted = extractPasswordValue(encodedPassword); + return isBcryptHash(extracted) && bcryptMatchesEmpty(extracted); } private String extractPasswordValue(String encodedPassword) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java index 045b93ce802..88f19f70d64 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java @@ -31,6 +31,27 @@ void emptyRawPassword_doesNotMatchNullStoredPassword() { assertThat(encoder.matches("", null)).isFalse(); } + @Test + void emptyRawPassword_doesNotMatchRawEmptyStoredValue() { + assertThat(encoder.matches("", "")).isFalse(); + } + + @Test + void emptyRawPassword_doesNotMatchEmptyBracesPrefix() { + assertThat(encoder.matches("", "{}")).isFalse(); + } + + @Test + void emptyRawPassword_doesNotMatchUnknownPrefixWithEmptyValue() { + assertThat(encoder.matches("", "{plaintext}")).isFalse(); + assertThat(encoder.matches("", "{argon2}")).isFalse(); + } + + @Test + void emptyRawPassword_doesNotMatchBcryptPrefixWithoutHash() { + assertThat(encoder.matches("", "{bcrypt}")).isFalse(); + } + @Test void nonEmptyRawPassword_delegatesToWrappedEncoder() { String encoded = bcrypt.encode("secret"); From 11701603f235cd1cb60acc7a4621747ee4867ee1 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 19 May 2026 14:40:58 +0300 Subject: [PATCH 55/58] refactor: extract EmptyPasswordAwareEncoder constants Pull the {noop} legacy marker and the bcrypt hash prefixes into named constants so the allow-list is readable at a glance. --- .../uaa/authentication/EmptyPasswordAwareEncoder.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java index 49ad6871855..5652cdef9ad 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java @@ -3,6 +3,8 @@ import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.security.crypto.password.PasswordEncoder; +import java.util.Set; + /** * Wraps a {@link PasswordEncoder} to allow empty raw passwords. * Spring Security 7 {@code AbstractValidatingPasswordEncoder} returns {@code false} from {@code matches()} @@ -11,6 +13,9 @@ */ class EmptyPasswordAwareEncoder implements PasswordEncoder { + private static final String NOOP_PREFIX = "{noop}"; + private static final Set BCRYPT_HASH_PREFIXES = Set.of("$2a$", "$2b$", "$2y$"); + private final PasswordEncoder delegate; EmptyPasswordAwareEncoder(PasswordEncoder delegate) { @@ -36,7 +41,7 @@ private boolean emptyPasswordMatchesStoredHash(String encodedPassword) { return false; } - if ("{noop}".equals(encodedPassword)) { + if (NOOP_PREFIX.equals(encodedPassword)) { return true; } @@ -56,7 +61,7 @@ private String extractPasswordValue(String encodedPassword) { } private boolean isBcryptHash(String value) { - return value.startsWith("$2a$") || value.startsWith("$2b$") || value.startsWith("$2y$"); + return BCRYPT_HASH_PREFIXES.stream().anyMatch(value::startsWith); } private boolean bcryptMatchesEmpty(String hash) { From e9f9d78eda7e95490f13a3d81646a15f66f74a5d Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 19 May 2026 22:16:04 +0300 Subject: [PATCH 56/58] review: align fallback text with caught JacksonException --- .../identity/uaa/ratelimiting/internal/RateLimiterStatus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java b/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java index 81951db97a5..547af6f7a34 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/ratelimiting/internal/RateLimiterStatus.java @@ -77,7 +77,7 @@ public String toString() { json = OM.writerWithDefaultPrettyPrinter().writeValueAsString(this); } catch (JacksonException e) { - json = "JsonProcessingException (" + e.getMessage() + "): " + json = "JacksonException (" + e.getMessage() + "): " + "current: " + current + "fromSource: " + fromSource; } From ecb05e2fee8ce4e6ea403ee6212c6c3f29e5dbd8 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 19 May 2026 22:17:51 +0300 Subject: [PATCH 57/58] review: remove redundant @JsonCreator from UaaAuthority constructor --- .../java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java index fc12bca4c27..931810129f4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java @@ -51,7 +51,6 @@ public enum UaaAuthority implements GrantedAuthority { @Getter private final String userType; - @JsonCreator UaaAuthority(String userType, int value) { this.userType = userType; this.value = value; From d97ca2a93279cf600ff0f9c7775c50ea7faf7159 Mon Sep 17 00:00:00 2001 From: Georgi Genchev Date: Tue, 19 May 2026 22:34:24 +0300 Subject: [PATCH 58/58] review: only strip {bcrypt} prefix in EmptyPasswordAwareEncoder --- .../uaa/authentication/EmptyPasswordAwareEncoder.java | 8 +++----- .../uaa/authentication/EmptyPasswordAwareEncoderTest.java | 6 ++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java index 5652cdef9ad..6e12b2cf6af 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoder.java @@ -14,6 +14,7 @@ class EmptyPasswordAwareEncoder implements PasswordEncoder { private static final String NOOP_PREFIX = "{noop}"; + private static final String BCRYPT_PREFIX = "{bcrypt}"; private static final Set BCRYPT_HASH_PREFIXES = Set.of("$2a$", "$2b$", "$2y$"); private final PasswordEncoder delegate; @@ -50,11 +51,8 @@ private boolean emptyPasswordMatchesStoredHash(String encodedPassword) { } private String extractPasswordValue(String encodedPassword) { - int start = encodedPassword.indexOf('{'); - int end = encodedPassword.indexOf('}'); - - if (start == 0 && end > 0) { - return encodedPassword.substring(end + 1); + if (encodedPassword.startsWith(BCRYPT_PREFIX)) { + return encodedPassword.substring(BCRYPT_PREFIX.length()); } return encodedPassword; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java index 88f19f70d64..55b5a9cd306 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/EmptyPasswordAwareEncoderTest.java @@ -47,6 +47,12 @@ void emptyRawPassword_doesNotMatchUnknownPrefixWithEmptyValue() { assertThat(encoder.matches("", "{argon2}")).isFalse(); } + @Test + void emptyRawPassword_doesNotMatchUnknownPrefixWrappingBcryptEmptyHash() { + String bcryptEmpty = bcrypt.encode(""); + assertThat(encoder.matches("", "{sha256}" + bcryptEmpty)).isFalse(); + } + @Test void emptyRawPassword_doesNotMatchBcryptPrefixWithoutHash() { assertThat(encoder.matches("", "{bcrypt}")).isFalse();