[java-opensaml] 05/06: Add AuthnStatementValidator. Refactor address check in AbstractSubjectConfirmationValidator.
Brent Putman
putmanb at georgetown.edu
Fri Jan 24 23:06:10 EST 2020
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch master
in repository java-opensaml.
View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=9f4a72c2342b0bef130017d8b1409b28c6edc683
commit 9f4a72c2342b0bef130017d8b1409b28c6edc683
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Jan 23 06:14:46 2020 -0500
Add AuthnStatementValidator. Refactor address check in AbstractSubjectConfirmationValidator.
---
.../SAML2AssertionValidationParameters.java | 24 +++
.../impl/AbstractSubjectConfirmationValidator.java | 67 +------
.../assertion/impl/AssertionValidationSupport.java | 115 +++++++++++
.../assertion/impl/AuthnStatementValidator.java | 220 +++++++++++++++++++++
4 files changed, 366 insertions(+), 60 deletions(-)
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
index 02f2fca..32f83e6 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
@@ -17,6 +17,9 @@
package org.opensaml.saml.saml2.assertion;
+import org.opensaml.saml.saml2.core.AuthnStatement;
+import org.opensaml.saml.saml2.core.SubjectLocality;
+
/**
* Parameter keys used to store and retrieve static and dynamic parameters within a
* {@link org.opensaml.saml.common.assertion.ValidationContext}.
@@ -119,6 +122,27 @@ public final class SAML2AssertionValidationParameters {
*/
public static final String COND_ONE_TIME_USE_EXPIRES = STD_PREFIX + COND_INFIX + ".OneTimeUseExpires";
+ /**
+ * Carries a {@link java.lang.Boolean} flag which indicates whether the
+ * Address attribute of the {@link SubjectLocality} of an {@link AuthnStatement}
+ * should be checked.
+ */
+ public static final String STMT_AUTHN_CHECK_ADDRESS =
+ STD_PREFIX + STMT_INFIX + ".Authn.SubjectLocality.CheckAddress";
+
+ /**
+ * Carries a {@link java.util.Set}<code><</code>{@link java.net.InetAddress}<code>></code>
+ * whose values are the acceptable
+ * values for the Address attribute of the {@link SubjectLocality} of an {@link AuthnStatement}.
+ */
+ public static final String STMT_AUTHN_VALID_ADDRESSES =
+ STD_PREFIX + STMT_INFIX + ".Authn.SubjectLocality.ValidAddresses";
+
+ /**
+ * Carries a {@link java.time.Duration} used to evaluate the
+ * allowed value of an AuthnInstant attribute of an {@link AuthnStatement}.
+ */
+ public static final String STMT_AUTHN_MAX_TIME = STD_PREFIX + STMT_INFIX + ".Authn.MaxTimeSinceAuthn";
/** Constructor. */
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java
index 4047b52..3dc97ff 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AbstractSubjectConfirmationValidator.java
@@ -17,17 +17,12 @@
package org.opensaml.saml.saml2.assertion.impl;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.time.Instant;
-import java.util.Arrays;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
import org.opensaml.saml.common.assertion.AssertionValidationException;
import org.opensaml.saml.common.assertion.ValidationContext;
import org.opensaml.saml.common.assertion.ValidationResult;
@@ -39,6 +34,8 @@ import org.opensaml.saml.saml2.core.SubjectConfirmation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
/**
* A base class for {@link SubjectConfirmationValidator} implementations.
*
@@ -244,7 +241,6 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
*
* @throws AssertionValidationException thrown if there is a problem determining the validity of the address
*/
- // Checkstyle: CyclomaticComplexity OFF
@Nonnull protected ValidationResult validateAddress(@Nonnull final SubjectConfirmation confirmation,
@Nonnull final Assertion assertion, @Nonnull final ValidationContext context)
throws AssertionValidationException {
@@ -253,66 +249,17 @@ public abstract class AbstractSubjectConfirmationValidator implements SubjectCon
(Boolean) context.getStaticParameters().get(SAML2AssertionValidationParameters.SC_CHECK_ADDRESS);
if (checkAddress != null && !checkAddress) {
- log.debug("SubjectConfirmationData at Address check is disabled, skipping");
+ log.debug("SubjectConfirmationData/@Address check is disabled, skipping");
return ValidationResult.VALID;
}
final String address = StringSupport.trimOrNull(confirmation.getSubjectConfirmationData().getAddress());
- if (address == null) {
- return ValidationResult.VALID;
- }
-
- log.debug("Evaluating SubjectConfirmationData at Address of : {}", address);
-
- final InetAddress[] confirmingAddresses;
- try {
- confirmingAddresses = InetAddress.getAllByName(address);
- } catch (final UnknownHostException e) {
- log.warn("The subject confirmation address '{}' in assetion '{}' can not be resolved "
- + "to a valid set of IP address(s)", address, assertion.getID());
- context.setValidationFailureMessage(String.format(
- "Subject confirmation address '%s' is not resolvable hostname or IP address", address));
- return ValidationResult.INDETERMINATE;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("SubjectConfirmationData/@Address was resolved to addresses: {}",
- Arrays.asList(confirmingAddresses));
- }
-
- final Set<InetAddress> validAddresses;
- try {
- validAddresses = (Set<InetAddress>) context.getStaticParameters().get(
- SAML2AssertionValidationParameters.SC_VALID_ADDRESSES);
- } catch (final ClassCastException e) {
- log.warn("The value of the static validation parameter '{}' was not java.util.Set<InetAddress>",
- SAML2AssertionValidationParameters.SC_VALID_ADDRESSES);
- context.setValidationFailureMessage("Unable to determine list of valid subject confirmation addresses");
- return ValidationResult.INDETERMINATE;
- }
- if (validAddresses == null || validAddresses.isEmpty()) {
- log.warn("Set of valid addresses was not available from the validation context, "
- + "unable to evaluate SubjectConfirmationData at Address");
- context.setValidationFailureMessage("Unable to determine list of valid subject confirmation addresses");
- return ValidationResult.INDETERMINATE;
- }
-
- for (final InetAddress confirmingAddress : confirmingAddresses) {
- if (validAddresses.contains(confirmingAddress)) {
- log.debug("Matched SubjectConfirmationData address '{}' to valid address",
- confirmingAddress.getHostAddress());
- return ValidationResult.VALID;
- }
- }
- log.debug("Failed to match SubjectConfirmationData at Address to any supplied valid addresses", validAddresses);
-
- context.setValidationFailureMessage(String.format(
- "Subject confirmation address for asertion '%s' did not match any valid addresses", assertion
- .getID()));
- return ValidationResult.INVALID;
+ return AssertionValidationSupport.checkAddress(context, address,
+ SAML2AssertionValidationParameters.SC_VALID_ADDRESSES,
+ assertion,
+ "SubjectConfirmationData/@Address");
}
- // Checkstyle: CyclomaticComplexity ON
/**
* Performs any further validation required for the specific confirmation method implementation.
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java
new file mode 100644
index 0000000..dcdce3a
--- /dev/null
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AssertionValidationSupport.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.saml.saml2.assertion.impl;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.saml.common.assertion.ValidationContext;
+import org.opensaml.saml.common.assertion.ValidationResult;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Support methods for assertion validation.
+ */
+public final class AssertionValidationSupport {
+
+ /** Logger. */
+ private static final Logger LOG = LoggerFactory.getLogger(AssertionValidationSupport.class);
+
+ private AssertionValidationSupport() { }
+
+ /**
+ * Check an address from an assertion using valid values obtained from the validation context.
+ *
+ * @param context the validation context
+ * @param address the address to be evaluated
+ * @param validAddressesParam the name of the context parameter holding the set of valid addresses
+ * @param assertion the assertion which is the context for evaluation
+ * @param description a brief description string used in logging messages
+ *
+ * @return the validation result
+ */
+ @Nonnull public static ValidationResult checkAddress(@Nonnull final ValidationContext context,
+ @Nullable final String address,
+ @Nonnull final String validAddressesParam,
+ @Nonnull final Assertion assertion,
+ @Nonnull final String description) {
+
+ if (address == null) {
+ return ValidationResult.VALID;
+ }
+
+ LOG.debug("Evaluating {} value of: {}", description, address);
+
+ final InetAddress[] confirmingAddresses;
+ try {
+ confirmingAddresses = InetAddress.getAllByName(address);
+ } catch (final UnknownHostException e) {
+ LOG.warn("The {} value '{}' in assertion '{}' can not be resolved to a valid set of IP address(s)",
+ description, address, assertion.getID());
+ context.setValidationFailureMessage(String.format(
+ "%s '%s' is not resolvable to hostname or IP address", description, address));
+ return ValidationResult.INDETERMINATE;
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("{} was resolved to addresses: {}", description, Arrays.asList(confirmingAddresses));
+ }
+
+ final Set<InetAddress> validAddresses;
+ try {
+ validAddresses = (Set<InetAddress>) context.getStaticParameters().get(validAddressesParam);
+ } catch (final ClassCastException e) {
+ LOG.warn("The value of the static validation parameter '{}' was not a java.util.Set<InetAddress>",
+ validAddressesParam);
+ context.setValidationFailureMessage(String.format("Unable to determine list of valid values for %s",
+ description));
+ return ValidationResult.INDETERMINATE;
+ }
+
+ if (validAddresses == null || validAddresses.isEmpty()) {
+ LOG.warn("Set of valid addresses was not available from the validation context, unable to evaluate {}",
+ description);
+ context.setValidationFailureMessage(String.format("Unable to determine list of valid values for %s",
+ description));
+ return ValidationResult.INDETERMINATE;
+ }
+
+ for (final InetAddress confirmingAddress : confirmingAddresses) {
+ if (validAddresses.contains(confirmingAddress)) {
+ LOG.debug("Matched {} '{}' to valid address", description, confirmingAddress.getHostAddress());
+ return ValidationResult.VALID;
+ }
+ }
+
+ LOG.debug("Failed to match {} to any supplied valid addresses: {}", description, validAddresses);
+
+ context.setValidationFailureMessage(String.format(
+ "%s for assertion '%s' did not match any valid addresses", description, assertion.getID()));
+ return ValidationResult.INVALID;
+ }
+
+}
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AuthnStatementValidator.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AuthnStatementValidator.java
new file mode 100644
index 0000000..29c5aac
--- /dev/null
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/assertion/impl/AuthnStatementValidator.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.opensaml.saml.saml2.assertion.impl;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+import javax.xml.namespace.QName;
+
+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.SAML20AssertionValidator;
+import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
+import org.opensaml.saml.saml2.assertion.StatementValidator;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.AuthnContext;
+import org.opensaml.saml.saml2.core.AuthnStatement;
+import org.opensaml.saml.saml2.core.Statement;
+import org.opensaml.saml.saml2.core.SubjectLocality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * {@link StatementValidator} implementation for {@link AuthnStatement} conditions.
+ *
+ * <p>
+ * Supports the following {@link ValidationContext} static parameters:
+ * </p>
+ * <ul>
+ * <li>
+ * {@link SAML2AssertionValidationParameters#STMT_AUTHN_CHECK_ADDRESS}:
+ * Optional.
+ * </li>
+ * <li>
+ * {@link SAML2AssertionValidationParameters#STMT_AUTHN_VALID_ADDRESSES}:
+ * Required if {@link SAML2AssertionValidationParameters#STMT_AUTHN_CHECK_ADDRESS} is true or omitted,
+ * otherwise optional.
+ * </li>
+ * <li>
+ * {@link SAML2AssertionValidationParameters#STMT_AUTHN_MAX_TIME}:
+ * Optional.
+ * </li>
+ * </ul>
+ *
+ * <p>
+ * Supports the following {@link ValidationContext} dynamic parameters:
+ * </p>
+ * <ul>
+ * <li>None.</li>
+ * </ul>
+ *
+ */
+public class AuthnStatementValidator implements StatementValidator {
+
+ /** Logger. */
+ private Logger log = LoggerFactory.getLogger(AuthnStatementValidator.class);
+
+ /** {@inheritDoc} */
+ public QName getServicedStatement() {
+ return AuthnStatement.DEFAULT_ELEMENT_NAME;
+ }
+
+ /** {@inheritDoc} */
+ public ValidationResult validate(@Nonnull final Statement statement, @Nonnull final Assertion assertion,
+ @Nonnull final ValidationContext context) throws AssertionValidationException {
+
+ if (!(statement instanceof AuthnStatement)) {
+ log.warn("Statement '{}' of type '{}' in assertion '{}' was not an '{}' statement. Unable to process.",
+ new Object[] { statement.getElementQName(), statement.getSchemaType(), assertion.getID(),
+ getServicedStatement(), });
+ return ValidationResult.INDETERMINATE;
+ }
+
+ try {
+ final AuthnStatement authnStatement = (AuthnStatement) statement;
+
+ ValidationResult result = validateAuthnInstant(authnStatement, assertion, context);
+ if (result != ValidationResult.VALID) {
+ return result;
+ }
+
+ result = validateSubjectLocality(authnStatement, assertion, context);
+ if (result != ValidationResult.VALID) {
+ return result;
+ }
+
+ result = validateAuthnContext(authnStatement, assertion, context);
+ if (result != ValidationResult.VALID) {
+ return result;
+ }
+ } catch (final AssertionValidationException|RuntimeException e) {
+ log.warn("There was a problem determining AuthnStatement validity", e);
+ return ValidationResult.INDETERMINATE;
+ }
+
+ return ValidationResult.VALID;
+ }
+
+ /**
+ * Validate the authnInstant attribute of the {@link AuthnStatement}.
+ *
+ * @param authnStatement the current statement being validated
+ * @param assertion the current assertion being evaluated
+ * @param context the current validation context
+ *
+ * @return the validation result
+ *
+ * @throws AssertionValidationException if there is a fatal error during evaluation
+ */
+ protected ValidationResult validateAuthnInstant(@Nonnull final AuthnStatement authnStatement,
+ @Nonnull final Assertion assertion, @Nonnull final ValidationContext context)
+ throws AssertionValidationException {
+
+ final Duration maxTimeSinceAuthn =
+ (Duration) context.getStaticParameters().get(SAML2AssertionValidationParameters.STMT_AUTHN_MAX_TIME);
+
+ if (maxTimeSinceAuthn == null) {
+ log.debug("Max time since authn for evaluation of AuthnStatement/@AuthnInstant not supplied, skipping");
+ return ValidationResult.VALID;
+ }
+ log.debug("Max time since authn for evaluation of AuthnStatement/@AuthnInstant was: {}", maxTimeSinceAuthn);
+
+ final Instant authnInstant = authnStatement.getAuthnInstant();
+ if (authnInstant == null) {
+ log.warn("AuthnStatement/@AuthnInstant is required but was not supplied, failing");
+ return ValidationResult.INVALID;
+ }
+
+ final Duration clockSkew = SAML20AssertionValidator.getClockSkew(context);
+ final Instant latestValid = authnInstant.plus(maxTimeSinceAuthn).plus(clockSkew);
+ final Instant now = Instant.now();
+
+ if (now.isAfter(latestValid)) {
+ log.warn("AuthnStatement/@AuthnInstant '{}' eval failed, now is after latest valid (including skew) '{}'",
+ authnInstant, latestValid);
+ return ValidationResult.INVALID;
+ }
+
+ return ValidationResult.VALID;
+ }
+
+ /**
+ * Validate the {@link SubjectLocality}.
+ *
+ * @param authnStatement the current statement being validated
+ * @param assertion the current assertion being evaluated
+ * @param context the current validation context
+ *
+ * @return the validation result
+ *
+ * @throws AssertionValidationException if there is a fatal error during evaluation
+ */
+ protected ValidationResult validateSubjectLocality(@Nonnull final AuthnStatement authnStatement,
+ @Nonnull final Assertion assertion, @Nonnull final ValidationContext context)
+ throws AssertionValidationException {
+
+ final Boolean checkAddress = (Boolean)
+ context.getStaticParameters().get(SAML2AssertionValidationParameters.STMT_AUTHN_CHECK_ADDRESS);
+
+ if (checkAddress != null && !checkAddress) {
+ log.debug("SubjectLocality/@Address check is disabled, skipping");
+ return ValidationResult.VALID;
+ }
+
+ final SubjectLocality subjectLocality = authnStatement.getSubjectLocality();
+ if (subjectLocality == null || subjectLocality.getAddress() == null) {
+ log.debug("AuthnStatement contained no SubjectLocality/@Address, skipping");
+ return ValidationResult.VALID;
+ }
+
+ final String address = StringSupport.trimOrNull(subjectLocality.getAddress());
+
+ return AssertionValidationSupport.checkAddress(context, address,
+ SAML2AssertionValidationParameters.STMT_AUTHN_VALID_ADDRESSES,
+ assertion,
+ "SubjectLocality/@Address");
+ }
+
+ /**
+ * Validate the {@link AuthnContext}.
+ *
+ * <p>
+ * The default implementation is a no-op and always valid. Subclasses may override.
+ * </p>
+ *
+ * @param authnStatement the current statement being validated
+ * @param assertion the current assertion being evaluated
+ * @param context the current validation context
+ *
+ * @return the validation result
+ *
+ * @throws AssertionValidationException if there is a fatal error during evaluation
+ */
+ protected ValidationResult validateAuthnContext(@Nonnull final AuthnStatement authnStatement,
+ @Nonnull final Assertion assertion, @Nonnull final ValidationContext context)
+ throws AssertionValidationException {
+ // Default is no-op.
+ return ValidationResult.VALID;
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list