Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor getFaultStringOrReason to Return Optional and Update JavaDoc #1428

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,4 @@ _site

credentials.yml
.flattened-pom.xml
.mvn/.gradle-enterprise
.mvn/.develocity
30 changes: 0 additions & 30 deletions .mvn/develocity.xml

This file was deleted.

11 changes: 3 additions & 8 deletions .mvn/extensions.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>com.gradle</groupId>
<artifactId>develocity-maven-extension</artifactId>
<version>1.21.3</version>
</extension>
<extension>
<groupId>com.gradle</groupId>
<artifactId>common-custom-user-data-maven-extension</artifactId>
<version>2.0</version>
<groupId>io.spring.develocity.conventions</groupId>
<artifactId>develocity-conventions-maven-extension</artifactId>
<version>0.0.19</version>
</extension>
</extensions>
29 changes: 9 additions & 20 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES')}
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
Expand All @@ -63,8 +62,7 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES')}
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
Expand All @@ -82,8 +80,7 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES')}
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
Expand All @@ -101,8 +98,7 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES')}
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
Expand All @@ -120,8 +116,7 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES')}
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
Expand All @@ -137,8 +132,7 @@ pipeline {
options { timeout(time: 30, unit: 'MINUTES')}
environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
steps {
script {
Expand All @@ -161,8 +155,7 @@ pipeline {
KEYRING = credentials('spring-signing-secring.gpg')
PASSPHRASE = credentials('spring-gpg-passphrase')
STAGING_PROFILE_ID = credentials('spring-data-release-deployment-maven-central-staging-profile-id')
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}

steps {
Expand Down Expand Up @@ -234,8 +227,7 @@ pipeline {
KEYRING = credentials('spring-signing-secring.gpg')
PASSPHRASE = credentials('spring-gpg-passphrase')
STAGING_PROFILE_ID = credentials('spring-data-release-deployment-maven-central-staging-profile-id')
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}

steps {
Expand Down Expand Up @@ -294,17 +286,14 @@ pipeline {

environment {
ARTIFACTORY = credentials("${p['artifactory.credentials']}")
GRADLE_ENTERPRISE_CACHE = credentials("${p['gradle-enterprise-cache.credentials']}")
GRADLE_ENTERPRISE_ACCESS_KEY = credentials("${p['gradle-enterprise.access-key']}")
DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}

steps {
script {
docker.withRegistry('', "${p['dockerhub.credentials']}") {
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ' +
'GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USR} ' +
'GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PSW} ' +
'./mvnw -s settings.xml -Pjakarta-ee-10,distribute,docs ' +
'-Dartifactory.server=https://repo.spring.io ' +
"-Dartifactory.username=${ARTIFACTORY_USR} " +
Expand Down
3 changes: 0 additions & 3 deletions ci/build-and-deploy-to-artifactory.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ set -euo pipefail

RELEASE_TYPE=$1

export GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USR}
export GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PSW}

echo 'Deploying to Artifactory...'

MAVEN_OPTS="-Duser.name=spring-builds+jenkins -Duser.home=/tmp/jenkins-home" ./mvnw \
Expand Down
3 changes: 0 additions & 3 deletions ci/build-and-deploy-to-maven-central.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ set -euo pipefail
PROJECT_VERSION=$1
STAGING_REPOSITORY_ID=$2

export GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USR}
export GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PSW}

echo 'Staging on Maven Central...'

GNUPGHOME=/tmp/gpghome
Expand Down
3 changes: 1 addition & 2 deletions ci/pipeline.properties
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
# Credentials
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
dockerhub.credentials=hub.docker.com-springbuildmaster
gradle-enterprise-cache.credentials=gradle_enterprise_cache_user
gradle-enterprise.access-key=gradle_enterprise_secret_access_key
develocity.access-key=gradle_enterprise_secret_access_key
3 changes: 0 additions & 3 deletions ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

set -euo pipefail

export GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USR}
export GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PSW}

MAVEN_OPTS="-Duser.name=spring-builds+jenkins -Duser.home=/tmp/jenkins-home" \
./mvnw -s settings.xml \
-P${PROFILE} clean dependency:list test -Dsort -B -U
5 changes: 0 additions & 5 deletions settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@
<username>${env.ARTIFACTORY_USR}</username>
<password>${env.ARTIFACTORY_PSW}</password>
</server>
<server>
<id>ge.spring.io</id>
<username>${env.GRADLE_ENTERPRISE_CACHE_USERNAME}</username>
<password>${env.GRADLE_ENTERPRISE_CACHE_PASSWORD}</password>
</server>
</servers>

</settings>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapMessage;

import java.util.Optional;

/**
* Thrown by {@code SoapFaultMessageResolver} when the response message has a fault.
*
Expand Down Expand Up @@ -56,13 +58,17 @@ public QName getFaultCode() {
}

/**
* Returns the fault string or reason. For SOAP 1.1, this returns the fault string. For SOAP 1.2, this returns the
* fault reason for the default locale.
* Returns the fault string or reason from the SOAP fault.
* For SOAP 1.1, this method returns the fault string. For SOAP 1.2, it returns the fault reason for the default locale.
* <p>
* Note that this method returns the same value as {@link #getMessage()}.
* <p>
* Note that this message returns the same as {@link #getMessage()}.
* The returned value is wrapped in an {@link Optional}. If the SOAP fault is not present, an empty {@link Optional} is returned.
*
* @return an {@link Optional} containing the fault string or reason if present, or an empty {@link Optional} if the SOAP fault is not present.
*/
public String getFaultStringOrReason() {
return soapFault != null ? soapFault.getFaultStringOrReason() : null;
public Optional<String> getFaultStringOrReason() {
return Optional.ofNullable(this.soapFault)
.map(SoapFault::getFaultStringOrReason);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
Expand Down Expand Up @@ -59,6 +62,9 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;

/**
* A WS-Security endpoint interceptor based on Apache's WSS4J. This interceptor supports messages created by the
* {@link org.springframework.ws.soap.axiom.AxiomSoapMessageFactory} and the
Expand Down Expand Up @@ -138,6 +144,7 @@
* @author Jamin Hitchcock
* @author Rob Leland
* @author Lars Uffmann
* @author Andreas Winter
* @see <a href="http://ws.apache.org/wss4j/">Apache WSS4J 2.0</a>
* @since 2.3.0
*/
Expand Down Expand Up @@ -194,6 +201,8 @@ public class Wss4jSecurityInterceptor extends AbstractWsSecurityInterceptor impl
// To maintain same behavior as default, this flag is set to true
private boolean removeSecurityHeader = true;

private List<Pattern> signatureSubjectDnPatterns = emptyList();

/**
* Create a {@link WSSecurityEngine} by default.
*/
Expand Down Expand Up @@ -225,6 +234,15 @@ public void setSecurementActor(String securementActor) {
handler.setOption(WSHandlerConstants.ACTOR, securementActor);
}

/**
* Defines whether to use a single certificate or a whole certificate chain when constructing
* a BinarySecurityToken used for direct reference in signature.
* The default is "true", meaning that only a single certificate is used.
*/
public void setSecurementSignatureSingleCertificate(boolean useSingleCertificate) {
handler.setOption(WSHandlerConstants.USE_SINGLE_CERTIFICATE, useSingleCertificate);
}

public void setSecurementEncryptionCrypto(Crypto securementEncryptionCrypto) {
handler.setSecurementEncryptionCrypto(securementEncryptionCrypto);
}
Expand Down Expand Up @@ -485,6 +503,19 @@ public void setValidationSignatureCrypto(Crypto signatureCrypto) {
this.validationSignatureCrypto = signatureCrypto;
}

/**
* Certificate constraints which will be applied to the subject DN of the certificate used for
* signature validation, after trust verification of the certificate chain associated with the
* certificate.
*
* @param patterns A list of regex patterns which will be applied to the subject DN.
*
* @see <a href="https://ws.apache.org/wss4j/config.html">WSS4J configuration: SIG_SUBJECT_CERT_CONSTRAINTS</a>
*/
public void setValidationSubjectDnConstraints(List<Pattern> patterns) {
signatureSubjectDnPatterns = patterns;
}

/** Whether to enable signatureConfirmation or not. By default signatureConfirmation is enabled */
public void setEnableSignatureConfirmation(boolean enableSignatureConfirmation) {

Expand Down Expand Up @@ -670,6 +701,7 @@ protected RequestData initializeRequestData(MessageContext messageContext) {
// allow for qualified password types for .Net interoperability
requestData.setAllowNamespaceQualifiedPasswordTypes(true);

requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);
return requestData;
}

Expand Down Expand Up @@ -710,6 +742,8 @@ protected RequestData initializeValidationRequestData(MessageContext messageCont
// allow for qualified password types for .Net interoperability
requestData.setAllowNamespaceQualifiedPasswordTypes(true);

requestData.setSubjectCertConstraints(signatureSubjectDnPatterns);

return requestData;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

import static org.assertj.core.api.Assertions.*;

import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;

import org.junit.jupiter.api.Test;
import org.springframework.ws.WebServiceMessage;
Expand Down Expand Up @@ -121,4 +123,36 @@ public void testSignResponseWithSignatureUser() throws Exception {
assertXpathExists("Absent SignatureConfirmation element",
"/SOAP-ENV:Envelope/SOAP-ENV:Header/wsse:Security/ds:Signature", document);
}

@Test
public void testValidateCertificateSubjectDnConstraintsShouldMatchSubject() throws Exception {
SoapMessage message = createSignedTestSoapMessage();
MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage());
interceptor.secureMessage(message, messageContext);

interceptor.setValidationActions("Signature");
interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile(".*")));
assertThatCode(() ->interceptor.validateMessage(message, messageContext)).doesNotThrowAnyException();
}

@Test
public void testValidateCertificateSubjectDnConstraintsShouldFailForNotMatchingSubject() throws Exception {
SoapMessage message = createSignedTestSoapMessage();
MessageContext messageContext = getSoap11MessageContext(createSignedTestSoapMessage());
interceptor.secureMessage(message, messageContext);

interceptor.setValidationActions("Signature");
interceptor.setValidationSubjectDnConstraints(List.of(Pattern.compile("O=Some Other Company")));
Throwable catched = catchThrowable(() -> interceptor.validateMessage(message, messageContext));
assertThat(catched).isInstanceOf(Wss4jSecurityValidationException.class);
}

private SoapMessage createSignedTestSoapMessage() throws Exception {
interceptor.setSecurementActions("Signature");
interceptor.setSecurementSignatureKeyIdentifier("DirectReference");
interceptor.setSecurementSignatureSingleCertificate(false);
interceptor.setSecurementPassword("123456");
interceptor.setSecurementUsername("testkey");
return loadSoap11Message("empty-soap.xml");
}
}
Binary file modified spring-ws-security/src/test/resources/private.jks
Binary file not shown.