protected LogoutRequest buildLogoutRequest(String user, String sessionIdx)
      throws SSOAgentException {

    LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject();

    logoutReq.setID(SSOAgentUtils.createID());
    logoutReq.setDestination(ssoAgentConfig.getSAML2().getIdPURL());

    DateTime issueInstant = new DateTime();
    logoutReq.setIssueInstant(issueInstant);
    logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000));

    IssuerBuilder issuerBuilder = new IssuerBuilder();
    Issuer issuer = issuerBuilder.buildObject();
    issuer.setValue(ssoAgentConfig.getSAML2().getSPEntityId());
    logoutReq.setIssuer(issuer);

    NameID nameId = new NameIDBuilder().buildObject();
    nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
    nameId.setValue(user);
    logoutReq.setNameID(nameId);

    SessionIndex sessionIndex = new SessionIndexBuilder().buildObject();
    sessionIndex.setSessionIndex(sessionIdx);
    logoutReq.getSessionIndexes().add(sessionIndex);

    logoutReq.setReason("Single Logout");

    return logoutReq;
  }
  protected AuthnRequest buildAuthnRequest(HttpServletRequest request) throws SSOAgentException {

    IssuerBuilder issuerBuilder = new IssuerBuilder();
    Issuer issuer =
        issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "samlp");
    issuer.setValue(ssoAgentConfig.getSAML2().getSPEntityId());

    /* NameIDPolicy */
    NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
    NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
    nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
    nameIdPolicy.setSPNameQualifier("Issuer");
    nameIdPolicy.setAllowCreate(true);

    /* AuthnContextClass */
    AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
    AuthnContextClassRef authnContextClassRef =
        authnContextClassRefBuilder.buildObject(
            "urn:oasis:names:tc:SAML:2.0:assertion", "AuthnContextClassRef", "saml");
    authnContextClassRef.setAuthnContextClassRef(
        "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

    /* AuthnContex */
    RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
    RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
    requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
    requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);

    DateTime issueInstant = new DateTime();

    /* Creation of AuthRequestObject */
    AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
    AuthnRequest authRequest =
        authRequestBuilder.buildObject(
            "urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest", "samlp");

    authRequest.setForceAuthn(ssoAgentConfig.getSAML2().isForceAuthn());
    authRequest.setIsPassive(ssoAgentConfig.getSAML2().isPassiveAuthn());
    authRequest.setIssueInstant(issueInstant);
    authRequest.setProtocolBinding(ssoAgentConfig.getSAML2().getHttpBinding());
    authRequest.setAssertionConsumerServiceURL(ssoAgentConfig.getSAML2().getACSURL());
    authRequest.setIssuer(issuer);
    authRequest.setNameIDPolicy(nameIdPolicy);
    authRequest.setRequestedAuthnContext(requestedAuthnContext);
    authRequest.setID(SSOAgentUtils.createID());
    authRequest.setVersion(SAMLVersion.VERSION_20);
    authRequest.setDestination(ssoAgentConfig.getSAML2().getIdPURL());
    if (request.getAttribute(Extensions.LOCAL_NAME) != null) {
      authRequest.setExtensions((Extensions) request.getAttribute(Extensions.LOCAL_NAME));
    }

    /* Requesting Attributes. This Index value is registered in the IDP */
    if (ssoAgentConfig.getSAML2().getAttributeConsumingServiceIndex() != null
        && ssoAgentConfig.getSAML2().getAttributeConsumingServiceIndex().trim().length() > 0) {
      authRequest.setAttributeConsumingServiceIndex(
          Integer.parseInt(ssoAgentConfig.getSAML2().getAttributeConsumingServiceIndex()));
    }

    return authRequest;
  }
  /**
   * Validate the signature of a SAML2 Response and Assertion
   *
   * @param response SAML2 Response
   * @return true, if signature is valid.
   */
  protected void validateSignature(Response response, Assertion assertion)
      throws SSOAgentException {

    if (SSOAgentDataHolder.getInstance().getSignatureValidator() != null) {
      // Custom implemetation of signature validation
      SAMLSignatureValidator signatureValidatorUtility =
          (SAMLSignatureValidator) SSOAgentDataHolder.getInstance().getSignatureValidator();
      signatureValidatorUtility.validateSignature(response, assertion, ssoAgentConfig);
    } else {
      // If custom implementation not found, Execute the default implementation
      if (ssoAgentConfig.getSAML2().isResponseSigned()) {
        if (response.getSignature() == null) {
          throw new SSOAgentException(
              "SAML2 Response signing is enabled, but signature element not found in SAML2 Response element");
        } else {
          try {
            SignatureValidator validator =
                new SignatureValidator(
                    new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
            validator.validate(response.getSignature());
          } catch (ValidationException e) {
            if (log.isDebugEnabled()) {
              log.debug("Validation exception : ", e);
            }
            throw new SSOAgentException("Signature validation failed for SAML2 Response");
          }
        }
      }
      if (ssoAgentConfig.getSAML2().isAssertionSigned()) {
        if (assertion.getSignature() == null) {
          throw new SSOAgentException(
              "SAML2 Assertion signing is enabled, but signature element not found in SAML2 Assertion element");
        } else {
          try {
            SignatureValidator validator =
                new SignatureValidator(
                    new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
            validator.validate(assertion.getSignature());
          } catch (ValidationException e) {
            if (log.isDebugEnabled()) {
              log.debug("Validation exception : ", e);
            }
            throw new SSOAgentException("Signature validation failed for SAML2 Assertion");
          }
        }
      }
    }
  }
  /**
   * 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");
      }
    }
  }
  public void processResponse(HttpServletRequest request, HttpServletResponse response)
      throws SSOAgentException {

    String saml2SSOResponse =
        request.getParameter(SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_RESP);

    if (saml2SSOResponse != null) {
      String decodedResponse =
          new String(Base64.decode(saml2SSOResponse), Charset.forName("UTF-8"));
      XMLObject samlObject = SSOAgentUtils.unmarshall(decodedResponse);
      if (samlObject instanceof LogoutResponse) {
        // This is a SAML response for a single logout request from the SP
        doSLO(request);
      } else {
        processSSOResponse(request);
      }
      String relayState = request.getParameter(RelayState.DEFAULT_ELEMENT_LOCAL_NAME);

      if (relayState != null
          && !relayState.isEmpty()
          && !"null".equalsIgnoreCase(relayState)) { // additional
        // checks for incompetent IdPs
        ssoAgentConfig.getSAML2().setRelayState(relayState);
      }

    } else {
      throw new SSOAgentException("Invalid SAML2 Response. SAML2 Response can not be null.");
    }
  }
  public SAML2SSOManager(SSOAgentConfig ssoAgentConfig) throws SSOAgentException {

    /* Initializing the OpenSAML library, loading default configurations */
    this.ssoAgentConfig = ssoAgentConfig;
    // load custom Signature Validator Class
    String signerClassName = ssoAgentConfig.getSAML2().getSignatureValidatorImplClass();
    try {
      if (signerClassName != null) {
        SSOAgentDataHolder.getInstance()
            .setSignatureValidator(Class.forName(signerClassName).newInstance());
      }
    } catch (ClassNotFoundException e) {
      throw new SSOAgentException("Error loading custom signature validator class", e);
    } catch (IllegalAccessException e) {
      throw new SSOAgentException("Error loading custom signature validator class", e);
    } catch (InstantiationException e) {
      throw new SSOAgentException("Error loading custom signature validator class", e);
    }
    SSOAgentUtils.doBootstrap();
  }
  /**
   * Get Decrypted Assertion
   *
   * @param encryptedAssertion
   * @return
   * @throws Exception
   */
  protected Assertion getDecryptedAssertion(EncryptedAssertion encryptedAssertion)
      throws SSOAgentException {

    try {
      KeyInfoCredentialResolver keyResolver =
          new StaticKeyInfoCredentialResolver(
              new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));

      EncryptedKey key =
          encryptedAssertion.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0);
      Decrypter decrypter = new Decrypter(null, keyResolver, null);
      SecretKey dkey =
          (SecretKey)
              decrypter.decryptKey(
                  key, encryptedAssertion.getEncryptedData().getEncryptionMethod().getAlgorithm());
      Credential shared = SecurityHelper.getSimpleCredential(dkey);
      decrypter = new Decrypter(new StaticKeyInfoCredentialResolver(shared), null, null);
      decrypter.setRootInNewDocument(true);
      return decrypter.decrypt(encryptedAssertion);
    } catch (Exception e) {
      throw new SSOAgentException("Decrypted assertion error", e);
    }
  }
  protected void processSSOResponse(HttpServletRequest request) throws SSOAgentException {

    LoggedInSessionBean sessionBean = new LoggedInSessionBean();
    sessionBean.setSAML2SSO(sessionBean.new SAML2SSO());

    String saml2ResponseString =
        new String(
            Base64.decode(
                request.getParameter(SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_RESP)),
            Charset.forName("UTF-8"));
    Response saml2Response = (Response) SSOAgentUtils.unmarshall(saml2ResponseString);
    sessionBean.getSAML2SSO().setResponseString(saml2ResponseString);
    sessionBean.getSAML2SSO().setSAMLResponse(saml2Response);

    Assertion assertion = null;
    if (ssoAgentConfig.getSAML2().isAssertionEncrypted()) {
      List<EncryptedAssertion> encryptedAssertions = saml2Response.getEncryptedAssertions();
      EncryptedAssertion encryptedAssertion = null;
      if (!org.apache.commons.collections.CollectionUtils.isEmpty(encryptedAssertions)) {
        encryptedAssertion = encryptedAssertions.get(0);
        try {
          assertion = getDecryptedAssertion(encryptedAssertion);
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.debug("Assertion decryption failure : ", e);
          }
          throw new SSOAgentException("Unable to decrypt the SAML2 Assertion");
        }
      }
    } else {
      List<Assertion> assertions = saml2Response.getAssertions();
      if (assertions != null && !assertions.isEmpty()) {
        assertion = assertions.get(0);
      }
    }
    if (assertion == null) {
      if (isNoPassive(saml2Response)) {
        LOGGER.log(Level.FINE, "Cannot authenticate in passive mode");
        return;
      }
      throw new SSOAgentException("SAML2 Assertion not found in the Response");
    }

    String idPEntityIdValue = assertion.getIssuer().getValue();
    if (idPEntityIdValue == null || idPEntityIdValue.isEmpty()) {
      throw new SSOAgentException("SAML2 Response does not contain an Issuer value");
    } else if (!idPEntityIdValue.equals(ssoAgentConfig.getSAML2().getIdPEntityId())) {
      throw new SSOAgentException("SAML2 Response Issuer verification failed");
    }
    sessionBean.getSAML2SSO().setAssertion(assertion);
    // Cannot marshall SAML assertion here, before signature validation due to a weird issue in
    // OpenSAML

    // Get the subject name from the Response Object and forward it to login_action.jsp
    String subject = null;
    if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
      subject = assertion.getSubject().getNameID().getValue();
    }

    if (subject == null) {
      throw new SSOAgentException("SAML2 Response does not contain the name of the subject");
    }

    sessionBean.getSAML2SSO().setSubjectId(subject); // set the subject
    request.getSession().setAttribute(SSOAgentConstants.SESSION_BEAN_NAME, sessionBean);

    // validate audience restriction
    validateAudienceRestriction(assertion);

    // validate signature
    validateSignature(saml2Response, assertion);

    // Marshalling SAML2 assertion after signature validation due to a weird issue in OpenSAML
    sessionBean.getSAML2SSO().setAssertionString(marshall(assertion));

    ((LoggedInSessionBean) request.getSession().getAttribute(SSOAgentConstants.SESSION_BEAN_NAME))
        .getSAML2SSO()
        .setSubjectAttributes(getAssertionStatements(assertion));

    // For removing the session when the single sign out request made by the SP itself
    if (ssoAgentConfig.getSAML2().isSLOEnabled()) {
      String sessionId = assertion.getAuthnStatements().get(0).getSessionIndex();
      if (sessionId == null) {
        throw new SSOAgentException(
            "Single Logout is enabled but IdP Session ID not found in SAML2 Assertion");
      }
      ((LoggedInSessionBean) request.getSession().getAttribute(SSOAgentConstants.SESSION_BEAN_NAME))
          .getSAML2SSO()
          .setSessionIndex(sessionId);
      SSOAgentSessionManager.addAuthenticatedSession(request.getSession(false));
    }

    request.getSession().setAttribute(SSOAgentConstants.SESSION_BEAN_NAME, sessionBean);
  }
  /**
   * Handles the request for http post binding
   *
   * @param request The HTTP request with SAML2 message
   * @param response The HTTP response
   * @param isLogout Whether the request is a logout request
   * @throws SSOAgentException
   */
  public String buildPostRequest(
      HttpServletRequest request, HttpServletResponse response, boolean isLogout)
      throws SSOAgentException {

    RequestAbstractType requestMessage = null;
    if (!isLogout) {
      requestMessage = buildAuthnRequest(request);
      if (ssoAgentConfig.getSAML2().isRequestSigned()) {
        requestMessage =
            SSOAgentUtils.setSignature(
                (AuthnRequest) requestMessage,
                XMLSignature.ALGO_ID_SIGNATURE_RSA,
                new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
      }

    } else {
      LoggedInSessionBean sessionBean =
          (LoggedInSessionBean)
              request.getSession(false).getAttribute(SSOAgentConstants.SESSION_BEAN_NAME);
      if (sessionBean != null) {
        requestMessage =
            buildLogoutRequest(
                sessionBean.getSAML2SSO().getSubjectId(),
                sessionBean.getSAML2SSO().getSessionIndex());
        if (ssoAgentConfig.getSAML2().isRequestSigned()) {
          requestMessage =
              SSOAgentUtils.setSignature(
                  (LogoutRequest) requestMessage,
                  XMLSignature.ALGO_ID_SIGNATURE_RSA,
                  new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
        }
      } else {
        throw new SSOAgentException("SLO Request can not be built. SSO Session is null");
      }
    }
    String encodedRequestMessage =
        encodeRequestMessage(requestMessage, SAMLConstants.SAML2_POST_BINDING_URI);

    Map<String, String[]> paramsMap = new HashMap<String, String[]>();
    paramsMap.put(
        SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_AUTH_REQ,
        new String[] {encodedRequestMessage});
    if (ssoAgentConfig.getSAML2().getRelayState() != null) {
      paramsMap.put(
          RelayState.DEFAULT_ELEMENT_LOCAL_NAME,
          new String[] {ssoAgentConfig.getSAML2().getRelayState()});
    }

    // Add any additional parameters defined
    if (ssoAgentConfig.getQueryParams() != null && !ssoAgentConfig.getQueryParams().isEmpty()) {
      paramsMap.putAll(ssoAgentConfig.getQueryParams());
    }

    StringBuilder htmlParams = new StringBuilder();
    for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
      if (entry.getKey() != null && entry.getValue() != null && entry.getValue().length > 0) {
        for (String param : entry.getValue()) {
          htmlParams
              .append("<input type='hidden' name='")
              .append(entry.getKey())
              .append("' value='")
              .append(param)
              .append("'>\n");
        }
      }
    }
    String htmlPayload = ssoAgentConfig.getSAML2().getPostBindingRequestHTMLPayload();
    if (htmlPayload == null || !htmlPayload.contains("<!--$saml_params-->")) {
      htmlPayload =
          "<html>\n"
              + "<body>\n"
              + "<p>You are now redirected back to "
              + ssoAgentConfig.getSAML2().getIdPURL()
              + " \n"
              + "If the redirection fails, please click the post button.</p>\n"
              + "<form method='post' action='"
              + ssoAgentConfig.getSAML2().getIdPURL()
              + "'>\n"
              + "<p>\n"
              + htmlParams.toString()
              + "<button type='submit'>POST</button>\n"
              + "</p>\n"
              + "</form>\n"
              + "<script type='text/javascript'>\n"
              + "document.forms[0].submit();\n"
              + "</script>\n"
              + "</body>\n"
              + "</html>";
    } else {
      htmlPayload = htmlPayload.replace("<!--$saml_params-->", htmlParams.toString());
    }
    return htmlPayload;
  }
  /**
   * Returns the redirection URL with the appended SAML2 Request message
   *
   * @param request SAML 2 request
   * @return redirectionUrl
   */
  public String buildRedirectRequest(HttpServletRequest request, boolean isLogout)
      throws SSOAgentException {

    RequestAbstractType requestMessage = null;
    if (!isLogout) {
      requestMessage = buildAuthnRequest(request);
    } else {
      LoggedInSessionBean sessionBean =
          (LoggedInSessionBean)
              request.getSession(false).getAttribute(SSOAgentConstants.SESSION_BEAN_NAME);
      if (sessionBean != null) {
        requestMessage =
            buildLogoutRequest(
                sessionBean.getSAML2SSO().getSubjectId(),
                sessionBean.getSAML2SSO().getSessionIndex());
      } else {
        throw new SSOAgentException("SLO Request can not be built. SSO Session is NULL");
      }
    }
    String idpUrl = null;

    String encodedRequestMessage =
        encodeRequestMessage(requestMessage, SAMLConstants.SAML2_REDIRECT_BINDING_URI);
    StringBuilder httpQueryString =
        new StringBuilder(
            SSOAgentConstants.SAML2SSO.HTTP_POST_PARAM_SAML2_AUTH_REQ
                + "="
                + encodedRequestMessage);

    String relayState = ssoAgentConfig.getSAML2().getRelayState();
    if (relayState != null) {
      try {
        httpQueryString.append(
            "&"
                + RelayState.DEFAULT_ELEMENT_LOCAL_NAME
                + "="
                + URLEncoder.encode(relayState, "UTF-8").trim());
      } catch (UnsupportedEncodingException e) {
        throw new SSOAgentException(
            "Error occurred while URLEncoding " + RelayState.DEFAULT_ELEMENT_LOCAL_NAME, e);
      }
    }

    if (ssoAgentConfig.getQueryParams() != null && !ssoAgentConfig.getQueryParams().isEmpty()) {
      StringBuilder builder = new StringBuilder();
      for (Map.Entry<String, String[]> entry : ssoAgentConfig.getQueryParams().entrySet()) {
        if (entry.getKey() != null && entry.getValue() != null && entry.getValue().length > 0) {
          for (String param : entry.getValue()) {
            builder.append("&").append(entry.getKey()).append("=").append(param);
          }
        }
      }
      httpQueryString.append(builder);
    }

    if (ssoAgentConfig.getSAML2().isRequestSigned()) {
      SSOAgentUtils.addDeflateSignatureToHTTPQueryString(
          httpQueryString,
          new X509CredentialImpl(ssoAgentConfig.getSAML2().getSSOAgentX509Credential()));
    }

    if (ssoAgentConfig.getSAML2().getIdPURL().indexOf("?") > -1) {
      idpUrl = ssoAgentConfig.getSAML2().getIdPURL().concat("&").concat(httpQueryString.toString());
    } else {
      idpUrl = ssoAgentConfig.getSAML2().getIdPURL().concat("?").concat(httpQueryString.toString());
    }
    return idpUrl;
  }