protected NameID getNameID(SAMLMessageContext context, LogoutRequest request)
     throws DecryptionException {
   NameID id;
   if (request.getEncryptedID() != null) {
     Assert.notNull(
         context.getLocalDecrypter(), "Can't decrypt NameID, no decrypter is set in the context");
     id = (NameID) context.getLocalDecrypter().decrypt(request.getEncryptedID());
   } else {
     id = request.getNameID();
   }
   return id;
 }
  public void sendLogoutRequest(SAMLMessageContext context, SAMLCredential credential)
      throws SAMLException, MetadataProviderException, MessageEncodingException {

    // If no user is logged in we do not initialize the protocol.
    if (credential == null) {
      return;
    }

    IDPSSODescriptor idpDescriptor = (IDPSSODescriptor) context.getPeerEntityRoleMetadata();
    SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
    String binding = SAMLUtil.getLogoutBinding(idpDescriptor, spDescriptor);

    SingleLogoutService logoutServiceIDP =
        SAMLUtil.getLogoutServiceForBinding(idpDescriptor, binding);
    LogoutRequest logoutRequest = getLogoutRequest(context, credential, logoutServiceIDP);

    context.setCommunicationProfileId(getProfileIdentifier());
    context.setOutboundMessage(logoutRequest);
    context.setOutboundSAMLMessage(logoutRequest);
    context.setPeerEntityEndpoint(logoutServiceIDP);

    boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutRequestSigned();
    sendMessage(context, signMessage);

    SAMLMessageStorage messageStorage = context.getMessageStorage();
    if (messageStorage != null) {
      messageStorage.storeMessage(logoutRequest.getID(), logoutRequest);
    }
  }
  /**
   * Returns logout request message ready to be sent to the IDP.
   *
   * @param context message context
   * @param credential information about assertions used to log current user in
   * @param bindingService service used to deliver the request
   * @return logoutRequest to be sent to IDP
   * @throws SAMLException error creating the message
   * @throws MetadataProviderException error retrieving metadata
   */
  protected LogoutRequest getLogoutRequest(
      SAMLMessageContext context, SAMLCredential credential, Endpoint bindingService)
      throws SAMLException, MetadataProviderException {

    SAMLObjectBuilder<LogoutRequest> builder =
        (SAMLObjectBuilder<LogoutRequest>)
            builderFactory.getBuilder(LogoutRequest.DEFAULT_ELEMENT_NAME);
    LogoutRequest request = builder.buildObject();
    buildCommonAttributes(context.getLocalEntityId(), request, bindingService);

    // Add session indexes
    SAMLObjectBuilder<SessionIndex> sessionIndexBuilder =
        (SAMLObjectBuilder<SessionIndex>)
            builderFactory.getBuilder(SessionIndex.DEFAULT_ELEMENT_NAME);
    for (AuthnStatement statement : credential.getAuthenticationAssertion().getAuthnStatements()) {
      SessionIndex index = sessionIndexBuilder.buildObject();
      index.setSessionIndex(statement.getSessionIndex());
      request.getSessionIndexes().add(index);
    }

    if (request.getSessionIndexes().size() == 0) {
      throw new SAMLException("No session indexes to logout user for were found");
    }

    SAMLObjectBuilder<NameID> nameIDBuilder =
        (SAMLObjectBuilder<NameID>) builderFactory.getBuilder(NameID.DEFAULT_ELEMENT_NAME);
    NameID nameID = nameIDBuilder.buildObject();
    nameID.setFormat(credential.getNameID().getFormat());
    nameID.setNameQualifier(credential.getNameID().getNameQualifier());
    nameID.setSPNameQualifier(credential.getNameID().getSPNameQualifier());
    nameID.setSPProvidedID(credential.getNameID().getSPProvidedID());
    nameID.setValue(credential.getNameID().getValue());
    request.setNameID(nameID);

    return request;
  }
  public void processLogoutResponse(SAMLMessageContext context)
      throws SAMLException, org.opensaml.xml.security.SecurityException, ValidationException {

    SAMLObject message = context.getInboundSAMLMessage();

    // Verify type
    if (!(message instanceof LogoutResponse)) {
      log.debug("Received response is not of a Response object type");
      throw new SAMLException("Error validating SAML response");
    }
    LogoutResponse response = (LogoutResponse) message;

    // Make sure request was authenticated if required, authentication is done as part of the
    // binding processing
    if (!context.isInboundSAMLMessageAuthenticated()
        && context.getLocalExtendedMetadata().isRequireLogoutResponseSigned()) {
      log.debug(
          "Logout Response object is required to be signed by the entity policy: "
              + context.getInboundSAMLMessageId());
      throw new SAMLException("Logout Response object is required to be signed");
    }

    // Verify issue time
    DateTime time = response.getIssueInstant();
    if (!isDateTimeSkewValid(getResponseSkew(), time)) {
      log.debug("Response issue time is either too old or with date in the future");
      throw new SAMLException("Error validating SAML response");
    }

    // Verify response to field if present, set request if correct
    // The inResponseTo field is optional, SAML 2.0 Core, 1542
    SAMLMessageStorage messageStorage = context.getMessageStorage();
    if (messageStorage != null && response.getInResponseTo() != null) {
      XMLObject xmlObject = messageStorage.retrieveMessage(response.getInResponseTo());
      if (xmlObject == null) {
        log.debug(
            "InResponseToField doesn't correspond to sent message", response.getInResponseTo());
        throw new SAMLException("Error validating SAML response");
      } else if (xmlObject instanceof LogoutRequest) {
        // Expected
      } else {
        log.debug(
            "Sent request was of different type then received response",
            response.getInResponseTo());
        throw new SAMLException("Error validating SAML response");
      }
    }

    // Verify destination
    if (response.getDestination() != null) {
      SPSSODescriptor localDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();

      // Check if destination is correct on this SP
      List<SingleLogoutService> services = localDescriptor.getSingleLogoutServices();
      boolean found = false;
      for (SingleLogoutService service : services) {
        if (response.getDestination().equals(service.getLocation())
            && context.getInboundSAMLBinding().equals(service.getBinding())) {
          found = true;
          break;
        }
      }
      if (!found) {
        log.debug(
            "Destination of the response was not the expected value", response.getDestination());
        throw new SAMLException("Error validating SAML response");
      }
    }

    // Verify issuer
    if (response.getIssuer() != null) {
      Issuer issuer = response.getIssuer();
      verifyIssuer(issuer, context);
    }

    // Verify status
    String statusCode = response.getStatus().getStatusCode().getValue();
    if (StatusCode.SUCCESS_URI.equals(statusCode)) {
      log.trace("Single Logout was successful");
    } else if (StatusCode.PARTIAL_LOGOUT_URI.equals(statusCode)) {
      log.trace("Single Logout was partially successful");
    } else {
      String[] logMessage = new String[2];
      logMessage[0] = response.getStatus().getStatusCode().getValue();
      StatusMessage message1 = response.getStatus().getStatusMessage();
      if (message1 != null) {
        logMessage[1] = message1.getMessage();
      }
      log.warn("Received LogoutResponse has invalid status code", logMessage);
    }
  }
  protected void sendLogoutResponse(Status status, SAMLMessageContext context)
      throws MetadataProviderException, SAMLException, MessageEncodingException {

    SAMLObjectBuilder<LogoutResponse> responseBuilder =
        (SAMLObjectBuilder<LogoutResponse>)
            builderFactory.getBuilder(LogoutResponse.DEFAULT_ELEMENT_NAME);
    LogoutResponse logoutResponse = responseBuilder.buildObject();

    IDPSSODescriptor idpDescriptor = SAMLUtil.getIDPDescriptor(metadata, context.getPeerEntityId());
    SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
    String binding = SAMLUtil.getLogoutBinding(idpDescriptor, spDescriptor);
    SingleLogoutService logoutService = SAMLUtil.getLogoutServiceForBinding(idpDescriptor, binding);

    logoutResponse.setID(generateID());
    logoutResponse.setIssuer(getIssuer(context.getLocalEntityId()));
    logoutResponse.setVersion(SAMLVersion.VERSION_20);
    logoutResponse.setIssueInstant(new DateTime());
    logoutResponse.setInResponseTo(context.getInboundSAMLMessageId());
    logoutResponse.setDestination(logoutService.getLocation());
    logoutResponse.setStatus(status);

    context.setCommunicationProfileId(getProfileIdentifier());
    context.setOutboundMessage(logoutResponse);
    context.setOutboundSAMLMessage(logoutResponse);
    context.setPeerEntityEndpoint(logoutService);

    context.setPeerEntityId(idpDescriptor.getID());
    context.setPeerEntityRoleMetadata(idpDescriptor);

    boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutResponseSigned();
    sendMessage(context, signMessage);
  }
  public boolean processLogoutRequest(SAMLMessageContext context, SAMLCredential credential)
      throws SAMLException, MetadataProviderException, MessageEncodingException {

    SAMLObject message = context.getInboundSAMLMessage();

    // Verify type
    if (message == null || !(message instanceof LogoutRequest)) {
      log.warn("Received request is not of a LogoutRequest object type");
      throw new SAMLException("Error validating SAML request");
    }

    LogoutRequest logoutRequest = (LogoutRequest) message;

    // Make sure request was authenticated if required, authentication is done as part of the
    // binding processing
    if (!context.isInboundSAMLMessageAuthenticated()
        && context.getLocalExtendedMetadata().isRequireLogoutRequestSigned()) {
      log.warn(
          "Logout Request object is required to be signed by the entity policy: "
              + context.getInboundSAMLMessageId());
      Status status = getStatus(StatusCode.REQUEST_DENIED_URI, "Message signature is required");
      sendLogoutResponse(status, context);
      return false;
    }

    try {
      // Verify destination
      verifyEndpoint(context.getLocalEntityEndpoint(), logoutRequest.getDestination());
    } catch (SAMLException e) {
      log.warn(
          "Destination of the request {} does not match any singleLogout endpoint",
          logoutRequest.getDestination());
      Status status =
          getStatus(StatusCode.REQUEST_DENIED_URI, "Destination URL of the request is invalid");
      sendLogoutResponse(status, context);
      return false;
    }

    // Verify issuer
    if (logoutRequest.getIssuer() != null) {
      try {
        Issuer issuer = logoutRequest.getIssuer();
        verifyIssuer(issuer, context);
      } catch (SAMLException e) {
        log.warn(
            "Response issue time is either too old or with date in the future, id {}",
            context.getInboundSAMLMessageId());
        Status status =
            getStatus(StatusCode.REQUEST_DENIED_URI, "Issuer of the message is unknown");
        sendLogoutResponse(status, context);
        return false;
      }
    }

    // Verify issue time
    DateTime time = logoutRequest.getIssueInstant();
    if (!isDateTimeSkewValid(getResponseSkew(), time)) {
      log.warn(
          "Response issue time is either too old or with date in the future, id {}.",
          context.getInboundSAMLMessageId());
      Status status =
          getStatus(StatusCode.REQUESTER_URI, "Message has been issued too long time ago");
      sendLogoutResponse(status, context);
      return false;
    }

    // Check whether any user is logged in
    if (credential == null) {
      Status status = getStatus(StatusCode.UNKNOWN_PRINCIPAL_URI, "No user is logged in");
      sendLogoutResponse(status, context);
      return false;
    }

    // Find index for which the logout is requested
    boolean indexFound = false;
    if (logoutRequest.getSessionIndexes() != null && logoutRequest.getSessionIndexes().size() > 0) {
      for (AuthnStatement statement :
          credential.getAuthenticationAssertion().getAuthnStatements()) {
        String statementIndex = statement.getSessionIndex();
        if (statementIndex != null) {
          for (SessionIndex index : logoutRequest.getSessionIndexes()) {
            if (statementIndex.equals(index.getSessionIndex())) {
              indexFound = true;
            }
          }
        }
      }
    } else {
      indexFound = true;
    }

    // Fail if sessionIndex is not found in any assertion
    if (!indexFound) {

      // Check logout request still valid and store request
      if (logoutRequest.getNotOnOrAfter() != null) {
        // TODO store request for assertions possibly arriving later
      }

      Status status =
          getStatus(StatusCode.REQUESTER_URI, "The requested SessionIndex was not found");
      sendLogoutResponse(status, context);
      return false;
    }

    try {
      // Fail if NameId doesn't correspond to the currently logged user
      NameID nameID = getNameID(context, logoutRequest);
      if (nameID == null || !equalsNameID(credential.getNameID(), nameID)) {
        Status status =
            getStatus(StatusCode.UNKNOWN_PRINCIPAL_URI, "The requested NameID is invalid");
        sendLogoutResponse(status, context);
        return false;
      }
    } catch (DecryptionException e) {
      Status status = getStatus(StatusCode.RESPONDER_URI, "The NameID can't be decrypted");
      sendLogoutResponse(status, context);
      return false;
    }

    // Message is valid, let's logout
    Status status = getStatus(StatusCode.SUCCESS_URI, null);
    sendLogoutResponse(status, context);

    return true;
  }
  /**
   * Initializes SSO by creating AuthnRequest assertion and sending it to the IDP using the default
   * binding. Default IDP is used to send the request.
   *
   * @param options values specified by caller to customize format of sent request
   * @throws SAMLException error initializing SSO
   * @throws SAMLRuntimeException in case context doesn't contain required entities or contains
   *     invalid data
   * @throws MetadataProviderException error retrieving needed metadata
   * @throws MessageEncodingException error forming SAML message
   */
  public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileOptions options)
      throws SAMLException, MetadataProviderException, MessageEncodingException {

    // Verify we deal with a local SP
    if (!SPSSODescriptor.DEFAULT_ELEMENT_NAME.equals(context.getLocalEntityRole())) {
      throw new SAMLException(
          "WebSSO can only be initialized for local SP, but localEntityRole is: "
              + context.getLocalEntityRole());
    }

    // Load the entities from the context
    SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
    IDPSSODescriptor idpssoDescriptor = (IDPSSODescriptor) context.getPeerEntityRoleMetadata();
    ExtendedMetadata idpExtendedMetadata = context.getPeerExtendedMetadata();

    if (spDescriptor == null || idpssoDescriptor == null || idpExtendedMetadata == null) {
      throw new SAMLException(
          "SPSSODescriptor, IDPSSODescriptor or IDPExtendedMetadata are not present in the SAMLContext");
    }
    log.debug(
        "idpExtendedMetadata.getSigningAlgorithm=" + idpExtendedMetadata.getSigningAlgorithm());
    idpExtendedMetadata.setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512");

    SingleSignOnService ssoService =
        getSingleSignOnService(options, idpssoDescriptor, spDescriptor);
    AssertionConsumerService consumerService =
        getAssertionConsumerService(options, idpssoDescriptor, spDescriptor);

    AuthnRequest authRequest = getAuthnRequest(context, options, consumerService, ssoService);
    if (authRequest == null) {
      throw new SAMLException("Erreur dans getAuthnRequest null");
    }
    // authRequest.setForceAuthn(Boolean.TRUE);
    log.debug("getAuthnRequest.providerName=" + authRequest.getProviderName());
    // TODO optionally implement support for conditions, subject

    context.setCommunicationProfileId(getProfileIdentifier());
    context.setOutboundMessage(authRequest);
    context.setOutboundSAMLMessage(authRequest);
    context.setPeerEntityEndpoint(ssoService);
    context.setPeerEntityRoleMetadata(idpssoDescriptor);
    context.setPeerExtendedMetadata(idpExtendedMetadata);

    if (options.getRelayState() != null) {
      context.setRelayState(options.getRelayState());
    }

    boolean sign =
        spDescriptor.isAuthnRequestsSigned() || idpssoDescriptor.getWantAuthnRequestsSigned();
    log.debug("signature?" + sign + " avec http://www.w3.org/2001/04/xmldsig-more#rsa-sha512");
    context
        .getLocalExtendedMetadata()
        .setSigningAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512");
    sendMessage(context, sign);

    SAMLMessageStorage messageStorage = context.getMessageStorage();
    if (messageStorage != null) {
      messageStorage.storeMessage(authRequest.getID(), authRequest);
    }
  }
  /**
   * Returns AuthnRequest SAML message to be used to demand authentication from an IDP described
   * using idpEntityDescriptor, with an expected response to the assertionConsumer address.
   *
   * @param context message context
   * @param options preferences of message creation
   * @param assertionConsumer assertion consumer where the IDP should respond
   * @param bindingService service used to deliver the request
   * @return authnRequest ready to be sent to IDP
   * @throws SAMLException error creating the message
   * @throws MetadataProviderException error retreiving metadata
   */
  protected AuthnRequest getAuthnRequest(
      SAMLMessageContext context,
      WebSSOProfileOptions options,
      AssertionConsumerService assertionConsumer,
      SingleSignOnService bindingService)
      throws SAMLException, MetadataProviderException {

    SAMLObjectBuilder<AuthnRequest> builder =
        (SAMLObjectBuilder<AuthnRequest>)
            builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
    AuthnRequest request = builder.buildObject();

    request.setIsPassive(options.getPassive());
    request.setForceAuthn(options.getForceAuthN());
    request.setProviderName(options.getProviderName());
    request.setVersion(SAMLVersion.VERSION_20);
    if (options.getIncludeEidas()) {
      // ne sert à rien
      //            request.setAssertionConsumerServiceURL(options.getIssuer());
      //            IssuerBuilder issuerBuilder = new IssuerBuilder();
      //            Issuer issuer = issuerBuilder.buildObject();
      //            issuer.setFormat(NAME_ISSUER_FORMAT_EIDAS);
      //            log.debug("issuer="+options.getIssuer());
      //            issuer.setValue(options.getIssuer());
      //            request.setIssuer(issuer);
      NameIDPolicy nameIDPolicy = new NameIDPolicyBuilder().buildObject();
      nameIDPolicy.setFormat(NAME_POLICY_FORMAT_EIDAS);
      nameIDPolicy.setAllowCreate(true);
      request.setNameIDPolicy(nameIDPolicy);
      QName eidas = new QName("xmlns:eidas", "http://eidas.europa.eu/saml-extensions");
      request.getNamespaceManager().registerAttributeName(eidas);
      Extensions extEidas =
          new ExtensionsBuilder()
              .buildObject("urn:oasis:names:tc:SAML:2.0:protocol", "Extensions", "saml2p");
      // Extensions extEidas = new EidasExtensions();
      Collection<String> colAttr = options.getEidasAttributes();
      // XSAnyBuilder raBuild = new XSAnyBuilder();
      // <eidas:SPType>public</eidas:SPType>
      SPType pub =
          new SPTypeBuilder()
              .buildObject("http://eidas.europa.eu/saml-extensions", "SPType", "eidas");
      // pub.setTextContent(EIDAS_PUBLIC);
      pub.setSPType(EIDAS_PUBLIC);
      // XSAny attrs = new XSAnyBuilder().buildObject("http://eidas.europa.eu/saml-extensions",
      // "RequestedAttributes", "eidas");
      extEidas.getUnknownXMLObjects().add(pub);
      // XSAnyBuilder anyBuilder = (XSAnyBuilder)
      // Configuration.getBuilderFactory().getBuilder(XSAny.TYPE_NAME);
      String resAttrs =
          "<eidas:RequestedAttributes xmlns:eidas=\"http://eidas.europa.eu/saml-extensions\">";
      for (String attr : colAttr) {
        resAttrs += oneAttribute(attr);
      }
      resAttrs += "</eidas:RequestedAttributes>";
      log.debug("resAttrs=" + resAttrs);
      EidasExtensionConfiguration eidasExt = new EidasExtensionConfiguration();
      eidasExt.configureExtension();
      SAMLSchemaBuilder.addExtensionSchema("/schema/saml_eidas_extension.xsd");
      BasicParserPool ppMgr = new BasicParserPool();
      ppMgr.setNamespaceAware(true);
      try {
        ppMgr.setSchema(SAMLSchemaBuilder.getSAML11Schema());
      } catch (SAXException ex) {
        log.error("Erreur schema=" + ex);
        return null;
      }
      InputStream is = new ByteArrayInputStream(resAttrs.getBytes());
      Document domAttrsRaq = null;
      try {
        domAttrsRaq = ppMgr.parse(is);
      } catch (XMLParserException e) {
        log.error("Erreur dom=" + e);
        return null;
      }
      if (domAttrsRaq == null) {
        log.error("Erreur dom vide");
        return null;
      }
      RequestedAttributesUnmarshaller unMars = new RequestedAttributesUnmarshaller();
      XMLObject attrs = null;
      try {
        attrs = unMars.unmarshall(domAttrsRaq.getDocumentElement());
      } catch (UnmarshallingException e) {
        System.err.println("Erreur unMarsh error=" + e);
      }

      extEidas.getUnknownXMLObjects().add(attrs);
      request.setExtensions(extEidas);
    }
    buildCommonAttributes(context.getLocalEntityId(), request, bindingService);

    buildScoping(request, bindingService, options);
    builNameIDPolicy(request, options);
    buildAuthnContext(request, options);
    buildReturnAddress(request, assertionConsumer);

    return request;
  }