[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