/**
   * @param SAMLToken - SAML Assertion XML
   * @param contextAttributeName - SAML Attribute name containing Context data
   * @param verfiySignature
   * @return a base64 encoded string containing the context
   * @throws SAMLValidationException - if validation fails
   */
  public static List<AttributeStatement> validate(byte[] SAMLToken, SAMLConfiguration samlConfig)
      throws SAMLValidationException {
    if (logger.isDebugEnabled()) {
      logger.debug("validate(byte[], SAMLConfiguration) - start"); // $NON-NLS-1$
    }

    Decrypter samlDecrypter = samlConfig.getSamlDecrypter();
    SignatureTrustEngine sigTrustEngine = samlConfig.getTrustEngine();
    String myURI = samlConfig.getMyURI();
    MessageReplayRule replayRule = samlConfig.getReplayRule();

    BasicParserPool ppMgr = new BasicParserPool();
    ppMgr.setNamespaceAware(true);

    VerifySignatureType verfiySignature = samlConfig.getVerifySignature();

    List<AttributeStatement> attrStatements = null;
    if (SAMLToken != null) {
      try {

        InputStream in = new ByteArrayInputStream(SAMLToken);
        Document SAMLDoc;
        try {
          SAMLDoc = ppMgr.parse(in);
        } catch (XMLParserException e) {
          logger.error(
              "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
              e); //$NON-NLS-1$

          throw createSAMLValidationException("Error parsing SAML XML", true, e);
        }
        Element responseElement = SAMLDoc.getDocumentElement();

        SAMLType samlType = SAMLType.Response;
        if (responseElement == null) {
          throw createSAMLValidationException(
              "Missing SAML Encrypted Assertion or Assertion or Assertion Response", true);
        }
        if (!"Response".equals(responseElement.getLocalName())
            || !SAMLConstants.SAML20P_NS.equals(responseElement.getNamespaceURI())) {
          if (!"Assertion".equals(responseElement.getLocalName())
              || !SAMLConstants.SAML20_NS.equals(responseElement.getNamespaceURI())) {
            if (!"EncryptedAssertion".equals(responseElement.getLocalName())
                || !SAMLConstants.SAML20_NS.equals(responseElement.getNamespaceURI())) {
              throw createSAMLValidationException(
                  "Missing or invalid SAML Encrypted Assertion or Assertion or Assertion Response",
                  true);
            } else {
              samlType = SAMLType.EncryptedAssertion;
            }
          } else {
            samlType = SAMLType.Assertion;
          }
        } else {
          samlType = SAMLType.Response;
        }

        if (samlType == SAMLType.Response) {
          // Unmarshall SAML Assertion Response into an OpenSAML Java object.
          UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
          Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(responseElement);
          Response samlResponse;
          try {
            samlResponse = (Response) unmarshaller.unmarshall(responseElement);
          } catch (UnmarshallingException e) {
            logger.error(
                "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
                e); //$NON-NLS-1$

            throw createSAMLValidationException(
                "Error in unmarshalling SAML XML Document", true, e);
          }

          // Check the replay attack for Response
          if (replayRule != null) {
            BasicSAMLMessageContext messageContext = new BasicSAMLMessageContext();
            // messageContext.setInboundMessage(samlResponse);
            if (samlResponse.getIssuer() != null)
              messageContext.setInboundMessageIssuer(samlResponse.getIssuer().getValue());
            messageContext.setInboundSAMLMessageId(samlResponse.getID());

            try {
              replayRule.evaluate(messageContext);
            } catch (SecurityPolicyException e) {
              logger.error(
                  "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
                  e); //$NON-NLS-1$

              throw createSAMLValidationException("Possible Replay Attack for Response", false, e);
            }
          }

          // Validate the status code
          if (!StatusCode.SUCCESS_URI.equals(samlResponse.getStatus().getStatusCode().getValue())) {
            throw createSAMLValidationException("Invalid Status Code.", true);
          }

          boolean responseSignatureVerified =
              validateResponseSignature(samlResponse, sigTrustEngine, verfiySignature);

          // Get first encrypted Assertion, if not present get first unencrypted assertion
          int assertCount = samlResponse.getEncryptedAssertions().size();
          if (assertCount == 0) {
            assertCount = samlResponse.getAssertions().size();
            if (assertCount == 0) {
              throw createSAMLValidationException(
                  "No Assertion or EncryptedAssertion found in received response", true);
            } else {
              for (Assertion samlAssertion : samlResponse.getAssertions()) {
                attrStatements =
                    validateAssertion(
                        samlAssertion,
                        sigTrustEngine,
                        myURI,
                        replayRule,
                        verfiySignature,
                        responseSignatureVerified);
                break; // Use the first only
              }
            }
          } else {
            for (EncryptedAssertion samlEncryptedAssertion :
                samlResponse.getEncryptedAssertions()) {

              // Decryption

              Assertion samlAssertion = null;
              try {
                samlAssertion = samlDecrypter.decrypt(samlEncryptedAssertion);
              } catch (DecryptionException e) {
                logger.error(
                    "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
                    e); //$NON-NLS-1$

                throw createSAMLValidationException(
                    "Error Decrypting Received Encrypted Assertion", true, e);
              }
              attrStatements =
                  validateAssertion(
                      samlAssertion,
                      sigTrustEngine,
                      myURI,
                      replayRule,
                      verfiySignature,
                      responseSignatureVerified);

              break; // Use the first only
            }
          }
        } else if (samlType == SAMLType.EncryptedAssertion) {
          // Unmarshall SAML Encrypted Assertion into an OpenSAML Java object.
          UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
          Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(responseElement);
          EncryptedAssertion encryptedAssertion = null;
          try {
            encryptedAssertion = (EncryptedAssertion) unmarshaller.unmarshall(responseElement);
          } catch (UnmarshallingException e) {
            logger.error(
                "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
                e); //$NON-NLS-1$

            throw createSAMLValidationException(
                "Error in unmarshalling SAML XML Document", true, e);
          }

          boolean responseSignatureVerified = false;

          // Decryption

          Assertion samlAssertion = null;
          try {
            samlAssertion = samlDecrypter.decrypt(encryptedAssertion);
          } catch (DecryptionException e) {
            logger.error(
                "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
                e); //$NON-NLS-1$

            throw createSAMLValidationException(
                "Error Decrypting Received Encrypted Assertion", true, e);
          }

          attrStatements =
              validateAssertion(
                  samlAssertion,
                  sigTrustEngine,
                  myURI,
                  replayRule,
                  verfiySignature,
                  responseSignatureVerified);
        } else if (samlType == SAMLType.Assertion) {
          // Unmarshall SAML Assertion  into an OpenSAML Java object.
          UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
          Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(responseElement);
          Assertion samlAssertion = null;
          try {
            samlAssertion = (Assertion) unmarshaller.unmarshall(responseElement);
          } catch (UnmarshallingException e) {
            logger.error(
                "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType)",
                e); //$NON-NLS-1$

            throw createSAMLValidationException(
                "Error in unmarshalling SAML XML Document", true, e);
          }

          boolean responseSignatureVerified = false;

          attrStatements =
              validateAssertion(
                  samlAssertion,
                  sigTrustEngine,
                  myURI,
                  replayRule,
                  verfiySignature,
                  responseSignatureVerified);
        }
      } catch (SAMLValidationException e) {
        throw e;
      }
    }

    if (logger.isDebugEnabled()) {
      logger.debug(
          "validate(byte[], String, Decrypter, SignatureTrustEngine, String, MessageReplayRule, ParserPool, VerifySignatureType) - end"); //$NON-NLS-1$
    }
    return attrStatements;
  }
  private static List<AttributeStatement> validateAssertion(
      Assertion samlAssertion,
      SignatureTrustEngine sigTrustEngine,
      String myURI,
      MessageReplayRule replayRule,
      VerifySignatureType verifySignature,
      boolean responseSignatureVerified)
      throws SAMLValidationException {
    if (logger.isDebugEnabled()) {
      logger.debug(
          "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType) - start"); //$NON-NLS-1$
    }

    // Check the replay attack
    if (replayRule != null) {
      BasicSAMLMessageContext messageContext = new BasicSAMLMessageContext();
      // messageContext.setInboundMessage(samlResponse);
      if (samlAssertion.getIssuer() != null)
        messageContext.setInboundMessageIssuer(samlAssertion.getIssuer().getValue());
      messageContext.setInboundSAMLMessageId(samlAssertion.getID());

      try {
        replayRule.evaluate(messageContext);
      } catch (SecurityPolicyException e) {
        logger.error(
            "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType)",
            e); //$NON-NLS-1$

        throw createSAMLValidationException("Possible Replay Attack for Assertion", false, e);
      }
    }

    if (verifySignature != VerifySignatureType.never) {
      Signature signature = samlAssertion.getSignature();
      if (signature == null) {
        if (verifySignature == VerifySignatureType.force && !responseSignatureVerified) {
          throw createSAMLValidationException("Signature does exist in Assertion", true);
        }
      } else {
        verifySignature(signature, samlAssertion.getIssuer().getValue(), sigTrustEngine);
      }
    }
    DateTime dt = new DateTime();

    // get subject (code below only processes first Subject confirmation)
    Subject subject = samlAssertion.getSubject();
    SubjectSchemaValidator subjectSchemaValidator = new SubjectSchemaValidator();
    try {
      subjectSchemaValidator.validate(subject);
    } catch (ValidationException e) {
      logger.error(
          "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType)",
          e); //$NON-NLS-1$

      throw createSAMLValidationException("Subject validation failed: " + e.getMessage(), true, e);
    }
    List<SubjectConfirmation> subjectConfirmations = subject.getSubjectConfirmations();
    for (SubjectConfirmation subjectConfirmation : subjectConfirmations) {
      SubjectConfirmationSchemaValidator subjectConfirmationSchemaValidator =
          new SubjectConfirmationSchemaValidator();
      try {
        subjectConfirmationSchemaValidator.validate(subjectConfirmation);
      } catch (ValidationException e) {
        logger.error(
            "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType)",
            e); //$NON-NLS-1$

        throw createSAMLValidationException(
            "Subject Confirmation validation failed: " + e.getMessage(), true, e);
      }
      SubjectConfirmationData subjectConfirmationData =
          subjectConfirmation.getSubjectConfirmationData();
      try {
        subjectConfirmationSchemaValidator.validate(subjectConfirmation);
      } catch (ValidationException e) {
        logger.error(
            "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType)",
            e); //$NON-NLS-1$

        throw createSAMLValidationException(
            "Subject Confirmation validation failed: " + e.getMessage(), true, e);
      }

      // verify the validity of time using clock skew, subjectConfirmationData.getNotBefore() and
      // subjectConfirmationData.getNotOnOrAfter()@
      DateTime notBefore = subjectConfirmationData.getNotBefore();
      DateTime notAfter = subjectConfirmationData.getNotOnOrAfter();

      if (notBefore != null && dt.isBefore(notBefore)) {
        throw createSAMLValidationException("Subject confirmation expired.", true);
      }

      if (notAfter != null && (dt.equals(notAfter) || dt.isAfter(notAfter))) {
        throw createSAMLValidationException("Subject confirmation expired.", true);
      }
    }

    //		 validate conditions
    Conditions conditions = samlAssertion.getConditions();

    // Validate the spec

    ConditionsSpecValidator conditionValidator = new ConditionsSpecValidator();
    try {
      conditionValidator.validate(conditions);
    } catch (ValidationException e) {
      logger.error(
          "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType)",
          e); //$NON-NLS-1$

      throw createSAMLValidationException("Condition Validity Failed.", true, e);
    }

    // verify the validity of time using clock skew, conditions.getNotBefore() and
    // conditions.getNotOnOrAfter()@
    DateTime notBefore = conditions.getNotBefore();
    DateTime notAfter = conditions.getNotOnOrAfter();
    if (notBefore != null && dt.isBefore(notBefore)) {
      throw createSAMLValidationException("Assertion expired.", true);
    }

    if (notAfter != null && (dt.equals(notAfter) || dt.isAfter(notAfter))) {
      throw createSAMLValidationException("Assertion expired.", true);
    }

    for (Condition condition : conditions.getConditions()) {
      if (condition instanceof AudienceRestriction) {
        if (myURI != null && myURI.length() > 0) {
          boolean audiencePresent = false;
          boolean iAmOneOfTheAudience = false;
          AudienceRestriction audienceRestriction = (AudienceRestriction) condition;
          for (Audience audience : audienceRestriction.getAudiences()) {
            audiencePresent = true;
            String audienceURI = audience.getAudienceURI();
            if (myURI.equals(audienceURI)) {
              iAmOneOfTheAudience = true;
              break;
            }
          }
          if (!(audiencePresent && iAmOneOfTheAudience)) {
            throw createSAMLValidationException(
                "None of the audience is intended for me: " + myURI, false);
          }
        }
      }
    }

    List<AttributeStatement> asList = samlAssertion.getAttributeStatements();

    if (logger.isDebugEnabled()) {
      logger.debug(
          "validateAndExtractContext(Assertion, String, SignatureTrustEngine, String, MessageReplayRule, VerifySignatureType) - end"); //$NON-NLS-1$
    }
    return asList;
  }