/**
   * Validate the AudienceRestriction of SAML2 Response
   *
   * @param assertion SAML2 Assertion
   * @return validity
   */
  private void validateAudienceRestriction(Assertion assertion) throws SAMLSSOException {

    if (assertion != null) {
      Conditions conditions = assertion.getConditions();
      if (conditions != null) {
        List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
        if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
          for (AudienceRestriction audienceRestriction : audienceRestrictions) {
            if (CollectionUtils.isNotEmpty(audienceRestriction.getAudiences())) {
              boolean audienceFound = false;
              for (Audience audience : audienceRestriction.getAudiences()) {
                if (properties
                    .get(IdentityApplicationConstants.Authenticator.SAML2SSO.SP_ENTITY_ID)
                    .equals(audience.getAudienceURI())) {
                  audienceFound = true;
                  break;
                }
              }
              if (!audienceFound) {
                throw new SAMLSSOException("SAML Assertion Audience Restriction validation failed");
              }
            } else {
              throw new SAMLSSOException(
                  "SAML Response's AudienceRestriction doesn't contain Audiences");
            }
          }
        } else {
          throw new SAMLSSOException("SAML Response doesn't contain AudienceRestrictions");
        }
      } else {
        throw new SAMLSSOException("SAML Response doesn't contain Conditions");
      }
    }
  }
  /**
   * Validate the AudienceRestriction of SAML2 Response
   *
   * @param assertion SAML2 Assertion
   * @return validity
   */
  protected void validateAudienceRestriction(Assertion assertion) throws SSOAgentException {

    if (assertion != null) {
      Conditions conditions = assertion.getConditions();
      if (conditions != null) {
        List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
        if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
          boolean audienceFound = false;
          for (AudienceRestriction audienceRestriction : audienceRestrictions) {
            if (audienceRestriction.getAudiences() != null
                && !audienceRestriction.getAudiences().isEmpty()) {
              for (Audience audience : audienceRestriction.getAudiences()) {
                if (ssoAgentConfig.getSAML2().getSPEntityId().equals(audience.getAudienceURI())) {
                  audienceFound = true;
                  break;
                }
              }
            }
            if (audienceFound) {
              break;
            }
          }
          if (!audienceFound) {
            throw new SSOAgentException("SAML2 Assertion Audience Restriction validation failed");
          }
        } else {
          throw new SSOAgentException("SAML2 Response doesn't contain AudienceRestrictions");
        }
      } else {
        throw new SSOAgentException("SAML2 Response doesn't contain Conditions");
      }
    }
  }
  private Assertion buildSAMLAssertion(
      SAMLSSOAuthnReqDTO authReqDTO, DateTime notOnOrAfter, String sessionId)
      throws IdentityException {
    try {
      DateTime currentTime = new DateTime();
      Assertion samlAssertion = new AssertionBuilder().buildObject();
      samlAssertion.setID(SAMLSSOUtil.createID());
      samlAssertion.setVersion(SAMLVersion.VERSION_20);
      samlAssertion.setIssuer(SAMLSSOUtil.getIssuer());
      samlAssertion.setIssueInstant(currentTime);
      Subject subject = new SubjectBuilder().buildObject();

      NameID nameId = new NameIDBuilder().buildObject();
      if (authReqDTO.getUseFullyQualifiedUsernameAsSubject()) {
        nameId.setValue(authReqDTO.getUsername());
        nameId.setFormat(NameIdentifier.EMAIL);
      } else {
        nameId.setValue(MultitenantUtils.getTenantAwareUsername(authReqDTO.getUsername()));
        nameId.setFormat(authReqDTO.getNameIDFormat());
      }

      subject.setNameID(nameId);

      SubjectConfirmation subjectConfirmation = new SubjectConfirmationBuilder().buildObject();
      subjectConfirmation.setMethod(SAMLSSOConstants.SUBJECT_CONFIRM_BEARER);

      SubjectConfirmationData scData = new SubjectConfirmationDataBuilder().buildObject();
      scData.setRecipient(authReqDTO.getAssertionConsumerURL());
      scData.setNotOnOrAfter(notOnOrAfter);
      scData.setInResponseTo(authReqDTO.getId());
      subjectConfirmation.setSubjectConfirmationData(scData);

      subject.getSubjectConfirmations().add(subjectConfirmation);

      samlAssertion.setSubject(subject);

      AuthnStatement authStmt = new AuthnStatementBuilder().buildObject();
      authStmt.setAuthnInstant(new DateTime());

      AuthnContext authContext = new AuthnContextBuilder().buildObject();
      AuthnContextClassRef authCtxClassRef = new AuthnContextClassRefBuilder().buildObject();
      authCtxClassRef.setAuthnContextClassRef(AuthnContext.PASSWORD_AUTHN_CTX);
      authContext.setAuthnContextClassRef(authCtxClassRef);
      authStmt.setAuthnContext(authContext);
      if (authReqDTO.isDoSingleLogout()) {
        authStmt.setSessionIndex(sessionId);
      }
      samlAssertion.getAuthnStatements().add(authStmt);

      /*
       * If <AttributeConsumingServiceIndex> element is in the
       * <AuthnRequest> and
       * according to the spec 2.0 the subject MUST be in the assertion
       */
      Map<String, String> claims = SAMLSSOUtil.getAttributes(authReqDTO);
      if (claims != null) {
        samlAssertion.getAttributeStatements().add(buildAttributeStatement(claims));
      }

      AudienceRestriction audienceRestriction = new AudienceRestrictionBuilder().buildObject();
      Audience issuerAudience = new AudienceBuilder().buildObject();
      issuerAudience.setAudienceURI(authReqDTO.getIssuer());
      audienceRestriction.getAudiences().add(issuerAudience);
      if (authReqDTO.getRequestedAudiences() != null) {
        for (String requestedAudience : authReqDTO.getRequestedAudiences()) {
          Audience audience = new AudienceBuilder().buildObject();
          audience.setAudienceURI(requestedAudience);
          audienceRestriction.getAudiences().add(audience);
        }
      }
      Conditions conditions = new ConditionsBuilder().buildObject();
      conditions.setNotBefore(currentTime);
      conditions.setNotOnOrAfter(notOnOrAfter);
      conditions.getAudienceRestrictions().add(audienceRestriction);
      samlAssertion.setConditions(conditions);

      if (authReqDTO.getDoSignAssertions()) {
        SAMLSSOUtil.setSignature(
            samlAssertion,
            XMLSignature.ALGO_ID_SIGNATURE_RSA,
            new SignKeyDataHolder(authReqDTO.getUsername()));
      }

      return samlAssertion;
    } catch (Exception e) {
      log.error("Error when reading claim values for generating SAML Response", e);
      throw new IdentityException(
          "Error when reading claim values for generating SAML Response", e);
    }
  }
  /* (non-Javadoc)
   * @see com.vmware.identity.samlservice.SamlValidator#validate(java.lang.Object)
   */
  @Override
  public ValidationResult validate(AuthnRequestState t) {
    log.debug("Validating request {}", t);

    ValidationResult vr = null;

    try {
      Validate.notNull(t);

      HttpServletRequest httpRequest = t.getRequest();
      Validate.notNull(httpRequest);

      AuthnRequest request = t.getAuthnRequest();
      Validate.notNull(request);
      Validate.notNull(request.getIssuer());

      IdmAccessor accessor = t.getIdmAccessor();
      Validate.notNull(accessor);
      Validate.notNull(accessor.getTenant());

      // Validate assertion consumer service first, if that is valid, we can send SAML replies
      try {
        boolean validateACSWithMetadata = !this.isRequestSigned(t);
        String acsUrl =
            accessor.getAcsForRelyingParty(
                request.getIssuer().getValue(),
                request.getAssertionConsumerServiceIndex(),
                request.getAssertionConsumerServiceURL(),
                request.getProtocolBinding(),
                validateACSWithMetadata);

        t.setAcsUrl(acsUrl);
      } catch (IllegalStateException e) {
        // set validation result to 400
        log.debug("Caught illegal state exception while Validating {} returning 400", e.toString());
        vr = new ValidationResult(HttpServletResponse.SC_BAD_REQUEST, e.getMessage(), null);
      }

      // Validate ID
      if (vr == null && request.getID() == null) {
        vr = new ValidationResult(OasisNames.REQUESTER);
        log.debug("Validation FAILED - Request ID is missing");
      }

      // Validate version
      if (vr == null) {
        SAMLVersion version = request.getVersion();
        if ((version.getMajorVersion() > Shared.REQUIRED_SAML_VERSION.getMajorVersion())
            || version.getMajorVersion() == Shared.REQUIRED_SAML_VERSION.getMajorVersion()
                && version.getMinorVersion() > Shared.REQUIRED_SAML_VERSION.getMinorVersion()) {
          // version too high
          vr =
              new ValidationResult(
                  OasisNames.VERSION_MISMATCH, OasisNames.REQUEST_VERSION_TOO_HIGH);
          log.debug("Validation FAILED - Version is too high");
        } else if ((version.getMajorVersion() < Shared.REQUIRED_SAML_VERSION.getMajorVersion())
            || version.getMajorVersion() == Shared.REQUIRED_SAML_VERSION.getMajorVersion()
                && version.getMinorVersion() < Shared.REQUIRED_SAML_VERSION.getMinorVersion()) {
          // version too low
          vr =
              new ValidationResult(OasisNames.VERSION_MISMATCH, OasisNames.REQUEST_VERSION_TOO_LOW);
          log.debug("Validation FAILED - Version is too low");
        }
      }

      // Validate IssueInstant only if this is a new request (i.e. it had not pass been validated)
      if (vr == null && !t.isExistingRequest()) {
        DateTime dtPlus = request.getIssueInstant();
        DateTime dtMinus = request.getIssueInstant();
        DateTime instant = new DateTime();
        long clockTolerance = accessor.getClockTolerance();
        if (dtPlus == null) {
          vr = new ValidationResult(OasisNames.REQUESTER);
          log.debug("Validation FAILED - Issue Instant is missing");
        } else {
          dtPlus = dtPlus.plus(clockTolerance);
          dtMinus = dtMinus.minus(clockTolerance);
          // dtPlus must be after now and dtMinus must be before now
          //	in order to satisfy clock tolerance
          if (dtPlus.isBefore(instant) || dtMinus.isAfter(instant)) {
            vr = new ValidationResult(OasisNames.REQUESTER);
            log.debug("Validation FAILED - Issue Instant outside of clock tolerance");
            log.debug("clockTolerance {}", clockTolerance);
            log.debug("now {}", instant);
            log.debug("dtPlus {}", dtPlus.toString());
            log.debug("dtMinus {}", dtMinus.toString());
          }
        }
      }

      // Destination URL skipped, this is already done by OpenSAML when parsing

      // validate scoping if presenet
      if (vr == null) {
        vr = validateScoping(t);
      }

      // signature must NOT be included
      if (vr == null) {
        if (request.getSignature() != null) {
          log.debug("Validation FAILED - Signature MUST NOT be present");
          vr = new ValidationResult(OasisNames.REQUESTER, OasisNames.REQUEST_UNSUPPORTED);
        }
      }

      // ensure that we don't accept unsigned requests if configuration requires signing
      if (vr == null) {

        try {
          boolean mustBeSigned =
              accessor.getAuthnRequestsSignedForRelyingParty(request.getIssuer().getValue());
          this.validateSigning(mustBeSigned, t);
        } catch (IllegalStateException e) {
          // set validation result to request denied
          log.error("Validation FAILED - unsigned request detected, signing required");
          vr = new ValidationResult(OasisNames.RESPONDER, OasisNames.REQUEST_DENIED);
        }
      }

      // validate NameIDPolicy if present
      if (vr == null) {
        NameIDPolicy policy = request.getNameIDPolicy();
        if (policy != null) {
          String format = policy.getFormat();
          if (format != null
              && !format.equals(OasisNames.PERSISTENT)
              && !format.equals(OasisNames.EMAIL_ADDRESS)
              && !format.equals(SAMLNames.IDFORMAT_VAL_UPN.toString())) {
            log.error("Validation FAILED - unknown NameIDPolicy Format");
            vr = new ValidationResult(OasisNames.REQUESTER, OasisNames.INVALID_NAMEID_POLICY);
          }
        }
      }

      // validate conditions
      if (vr == null) {
        Conditions conditions = request.getConditions();
        if (conditions != null) {
          // notBefore processing
          DateTime notBefore = conditions.getNotBefore();
          if (notBefore != null) {
            // no additional validation, we'll use whatever client wants
            t.setStartTime(notBefore.toDate());
          }
          // delegable and renewable conditions
          for (Condition c : conditions.getConditions()) {
            if (c == null) {
              continue;
            }
            if (c instanceof RenewableType) {
              t.setRenewable(true);
            }
            if (c instanceof DelegableType) {
              t.setDelegable(true);
            }
          }
        }
      }
      if (vr == null) {
        computeSupportedAuthnTypes(t, request);
      }

      // validation done
      if (vr == null) {
        log.info("Authentication request validation succeeded");
        vr = new ValidationResult(); // success

        // check if we need to convert a principal into emailAddress
        if (request.getNameIDPolicy() != null
            && request.getNameIDPolicy().getFormat() != null
            && request.getNameIDPolicy().getFormat().equals(OasisNames.EMAIL_ADDRESS)) {
          t.setIdentityFormat(OasisNames.IDENTITY_FORMAT_EMAIL_ADDRESS);
        } else {
          t.setIdentityFormat(OasisNames.IDENTITY_FORMAT_UPN);
        }
      }

    } catch (Exception e) {
      vr = new ValidationResult(HttpServletResponse.SC_BAD_REQUEST, "BadRequest", null);
      log.debug("Caught exception while Validating " + e.toString() + ", returning 400");
    }
    return vr;
  }
  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;
  }