Skip to content

Commit

Permalink
Temporarily adding SSOValidatorResponse until we pick up CXF 3.1.13
Browse files Browse the repository at this point in the history
  • Loading branch information
coheigea committed Aug 11, 2017
1 parent fecfc6f commit c8748ba
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 41 deletions.
Expand Up @@ -19,6 +19,8 @@
package org.apache.syncope.core.logic;

import org.apache.syncope.core.logic.saml2.SAML2UserManager;
import org.apache.syncope.core.logic.saml2.SSOValidatorResponse;

import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.RandomBasedGenerator;
import java.io.OutputStream;
Expand All @@ -37,7 +39,6 @@
import org.apache.commons.lang3.tuple.Triple;
import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
import org.apache.cxf.rs.security.saml.sso.SSOValidatorResponse;
import org.apache.syncope.common.lib.AbstractBaseBean;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.to.AttrTO;
Expand Down
Expand Up @@ -43,7 +43,6 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
import org.apache.cxf.rs.security.saml.sso.SAMLProtocolResponseValidator;
import org.apache.cxf.rs.security.saml.sso.SSOValidatorResponse;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.syncope.common.lib.SSOConstants;
import org.apache.syncope.common.lib.types.SAML2BindingType;
Expand Down
Expand Up @@ -25,7 +25,6 @@
import org.w3c.dom.Element;

import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.rs.security.saml.sso.SSOValidatorResponse;
import org.apache.cxf.rs.security.saml.sso.TokenReplayCache;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.saml.builder.SAML2Constants;
Expand All @@ -39,9 +38,9 @@
*/
//CHECKSTYLE:OFF
public class SAMLSSOResponseValidator {

private static final Logger LOG = LogUtils.getL7dLogger(SAMLSSOResponseValidator.class);

private String issuerIDP;
private String assertionConsumerURL;
private String clientAddress;
Expand All @@ -51,22 +50,22 @@ public class SAMLSSOResponseValidator {
private boolean enforceAssertionsSigned = true;
private boolean enforceKnownIssuer = true;
private TokenReplayCache<String> replayCache;

/**
* Enforce that Assertions contained in the Response must be signed (if the Response itself is not
* signed). The default is true.
*/
public void setEnforceAssertionsSigned(boolean enforceAssertionsSigned) {
this.enforceAssertionsSigned = enforceAssertionsSigned;
}

/**
* Enforce that the Issuer of the received Response/Assertion is known. The default is true.
*/
public void setEnforceKnownIssuer(boolean enforceKnownIssuer) {
this.enforceKnownIssuer = enforceKnownIssuer;
}

/**
* Validate a SAML 2 Protocol Response
* @param samlResponse
Expand All @@ -86,7 +85,7 @@ public SSOValidatorResponse validateSamlResponse(
LOG.fine("The Response must contain at least one Assertion");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// The Response must contain a Destination that matches the assertionConsumerURL if it is
// signed
String destination = samlResponse.getDestination();
Expand All @@ -95,12 +94,12 @@ public SSOValidatorResponse validateSamlResponse(
LOG.fine("The Response must contain a destination that matches the assertion consumer URL");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

if (enforceResponseSigned && !samlResponse.isSigned()) {
LOG.fine("The Response must be signed!");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// Validate Assertions
org.opensaml.saml.saml2.core.Assertion validAssertion = null;
Date sessionNotOnOrAfter = null;
Expand All @@ -111,17 +110,17 @@ public SSOValidatorResponse validateSamlResponse(
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
validateIssuer(assertion.getIssuer());

if (!samlResponse.isSigned() && enforceAssertionsSigned && assertion.getSignature() == null) {
LOG.fine("The enclosed assertions in the SAML Response must be signed");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// Check for AuthnStatements and validate the Subject accordingly
if (assertion.getAuthnStatements() != null
&& !assertion.getAuthnStatements().isEmpty()) {
org.opensaml.saml.saml2.core.Subject subject = assertion.getSubject();
org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf =
org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf =
validateAuthenticationSubject(subject, assertion.getID(), postBinding);
if (subjectConf != null) {
validateAudienceRestrictionCondition(assertion.getConditions());
Expand All @@ -139,52 +138,53 @@ public SSOValidatorResponse validateSamlResponse(
}
}
}

if (validAssertion == null) {
LOG.fine("The Response did not contain any Authentication Statement that matched "
+ "the Subject Confirmation criteria");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

SSOValidatorResponse validatorResponse = new SSOValidatorResponse();
validatorResponse.setResponseId(samlResponse.getID());
validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter);
validatorResponse.setOpensamlAssertion(validAssertion);
if (samlResponse.getIssueInstant() != null) {
validatorResponse.setCreated(samlResponse.getIssueInstant().toDate());
}

Element assertionElement = validAssertion.getDOM();
Element clonedAssertionElement = (Element)assertionElement.cloneNode(true);
validatorResponse.setAssertionElement(clonedAssertionElement);
validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement));

return validatorResponse;
}

/**
* Validate the Issuer (if it exists)
*/
private void validateIssuer(org.opensaml.saml.saml2.core.Issuer issuer) throws WSSecurityException {
if (issuer == null) {
return;
}

// Issuer value must match (be contained in) Issuer IDP
if (enforceKnownIssuer && !issuerIDP.startsWith(issuer.getValue())) {
LOG.fine("Issuer value: " + issuer.getValue() + " does not match issuer IDP: "
LOG.fine("Issuer value: " + issuer.getValue() + " does not match issuer IDP: "
+ issuerIDP);
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// Format must be nameid-format-entity
if (issuer.getFormat() != null
&& !SAML2Constants.NAMEID_FORMAT_ENTITY.equals(issuer.getFormat())) {
LOG.fine("Issuer format is not null and does not equal: "
LOG.fine("Issuer format is not null and does not equal: "
+ SAML2Constants.NAMEID_FORMAT_ENTITY);
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}

/**
* Validate the Subject (of an Authentication Statement).
*/
Expand All @@ -194,20 +194,20 @@ private org.opensaml.saml.saml2.core.SubjectConfirmation validateAuthenticationS
if (subject.getSubjectConfirmations() == null) {
return null;
}

org.opensaml.saml.saml2.core.SubjectConfirmation validSubjectConf = null;
// We need to find a Bearer Subject Confirmation method
for (org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf
for (org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf
: subject.getSubjectConfirmations()) {
if (SAML2Constants.CONF_BEARER.equals(subjectConf.getMethod())) {
validateSubjectConfirmation(subjectConf.getSubjectConfirmationData(), id, postBinding);
validSubjectConf = subjectConf;
}
}

return validSubjectConf;
}

/**
* Validate a (Bearer) Subject Confirmation
*/
Expand All @@ -218,22 +218,22 @@ private void validateSubjectConfirmation(
LOG.fine("Subject Confirmation Data of a Bearer Subject Confirmation is null");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// Recipient must match assertion consumer URL
String recipient = subjectConfData.getRecipient();
if (recipient == null || !recipient.equals(assertionConsumerURL)) {
LOG.fine("Recipient " + recipient + " does not match assertion consumer URL "
+ assertionConsumerURL);
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// We must have a NotOnOrAfter timestamp
if (subjectConfData.getNotOnOrAfter() == null
|| subjectConfData.getNotOnOrAfter().isBeforeNow()) {
LOG.fine("Subject Conf Data does not contain NotOnOrAfter or it has expired");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// Need to keep bearer assertion IDs based on NotOnOrAfter to detect replay attacks
if (postBinding && replayCache != null) {
if (replayCache.getId(id) == null) {
Expand All @@ -246,21 +246,21 @@ private void validateSubjectConfirmation(
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}

// Check address
if (subjectConfData.getAddress() != null && clientAddress != null
&& !subjectConfData.getAddress().equals(clientAddress)) {
LOG.fine("Subject Conf Data address " + subjectConfData.getAddress() + " does match"
+ " client address " + clientAddress);
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// It must not contain a NotBefore timestamp
if (subjectConfData.getNotBefore() != null) {
LOG.fine("The Subject Conf Data must not contain a NotBefore timestamp");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

// InResponseTo must match the AuthnRequest request Id
if (requestId != null && !requestId.equals(subjectConfData.getInResponseTo())) {
LOG.fine("The InResponseTo String does match the original request id " + requestId);
Expand All @@ -269,9 +269,9 @@ private void validateSubjectConfirmation(
LOG.fine("No InResponseTo String is allowed for the unsolicted case");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}

}

private void validateAudienceRestrictionCondition(
org.opensaml.saml.saml2.core.Conditions conditions
) throws WSSecurityException {
Expand All @@ -281,13 +281,13 @@ private void validateAudienceRestrictionCondition(
}
List<AudienceRestriction> audienceRestrs = conditions.getAudienceRestrictions();
if (!matchSaml2AudienceRestriction(spIdentifier, audienceRestrs)) {
LOG.fine("Assertion does not contain unique subject provider identifier "
LOG.fine("Assertion does not contain unique subject provider identifier "
+ spIdentifier + " in the audience restriction conditions");
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
}
}


private boolean matchSaml2AudienceRestriction(
String appliesTo, List<AudienceRestriction> audienceRestrictions
) {
Expand Down Expand Up @@ -352,7 +352,7 @@ public String getSpIdentifier() {
public void setSpIdentifier(String spIdentifier) {
this.spIdentifier = spIdentifier;
}

public void setReplayCache(TokenReplayCache<String> replayCache) {
this.replayCache = replayCache;
}
Expand All @@ -367,5 +367,5 @@ public boolean isEnforceResponseSigned() {
public void setEnforceResponseSigned(boolean enforceResponseSigned) {
this.enforceResponseSigned = enforceResponseSigned;
}

}
@@ -0,0 +1,84 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.logic.saml2;

import java.util.Date;

import org.w3c.dom.Element;
import org.opensaml.saml.saml2.core.Assertion;

/**
* Some information that encapsulates a successful validation by the SAMLSSOResponseValidator
*/
public class SSOValidatorResponse {
private Date sessionNotOnOrAfter;
private Date created;
private String responseId;
private String assertion;
private Element assertionElement;
private Assertion opensamlAssertion;

public String getAssertion() {
return assertion;
}

public void setAssertion(final String assertion) {
this.assertion = assertion;
}

public Date getSessionNotOnOrAfter() {
return sessionNotOnOrAfter;
}

public void setSessionNotOnOrAfter(final Date sessionNotOnOrAfter) {
this.sessionNotOnOrAfter = sessionNotOnOrAfter;
}

public String getResponseId() {
return responseId;
}

public void setResponseId(final String responseId) {
this.responseId = responseId;
}

public Element getAssertionElement() {
return assertionElement;
}

public void setAssertionElement(final Element assertionElement) {
this.assertionElement = assertionElement;
}

public Date getCreated() {
return created;
}

public void setCreated(final Date created) {
this.created = created;
}

public Assertion getOpensamlAssertion() {
return opensamlAssertion;
}

public void setOpensamlAssertion(final Assertion opensamlAssertion) {
this.opensamlAssertion = opensamlAssertion;
}
}

0 comments on commit c8748ba

Please sign in to comment.