All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.xpack.security.authc.saml.SamlLogoutRequestHandler Maven / Gradle / Ivy

There is a newer version: 8.17.0
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */
package org.elasticsearch.xpack.security.authc.saml;

import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.core.TimeValue;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.saml2.core.EncryptedID;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.xmlsec.encryption.support.DecryptionException;
import org.opensaml.xmlsec.signature.Signature;
import org.w3c.dom.Element;

import java.time.Clock;
import java.util.Objects;

import static org.elasticsearch.core.Strings.format;
import static org.elasticsearch.xpack.security.authc.saml.SamlUtils.samlException;

/**
 * Processes a LogoutRequest for an IdP-initiated logout.
 */
public class SamlLogoutRequestHandler extends SamlObjectHandler {

    private static final String REQUEST_TAG_NAME = "LogoutRequest";

    SamlLogoutRequestHandler(Clock clock, IdpConfiguration idp, SpConfiguration sp, TimeValue maxSkew) {
        super(clock, idp, sp, maxSkew);
    }

    /**
     * Processes the provided LogoutRequest and extracts the NameID and SessionIndex.
     * Returns these in a {@link SamlAttributes} object with an empty attributes list.
     * 

* The recommended binding for Logout (for maximum interoperability) is HTTP-Redirect. * Under this binding the signature is applied to the query-string (including parameter * names and url-encoded/base64-encoded/deflated values). Therefore in order to properly * validate the signature, this method operates on a raw query- string. * * @throws ElasticsearchSecurityException If the SAML is invalid for this realm/configuration */ public Result parseFromQueryString(String queryString) { final ParsedQueryString parsed = parseQueryStringAndValidateSignature(queryString, "SAMLRequest"); final Element root = parseSamlMessage(inflate(decodeBase64(parsed.samlMessage))); if (REQUEST_TAG_NAME.equals(root.getLocalName()) && SAML_NAMESPACE.equals(root.getNamespaceURI())) { try { final LogoutRequest logoutRequest = buildXmlObject(root, LogoutRequest.class); return parseLogout(logoutRequest, parsed.hasSignature == false, parsed.relayState); } catch (ElasticsearchSecurityException e) { logger.trace("Rejecting SAML logout request {} because {}", SamlUtils.toString(root), e.getMessage()); throw e; } } else { throw samlException( "SAML content [{}] should have a root element of Namespace=[{}] Tag=[{}]", root, SAML_NAMESPACE, REQUEST_TAG_NAME ); } } private Result parseLogout(LogoutRequest logoutRequest, boolean requireSignature, String relayState) { final Signature signature = logoutRequest.getSignature(); if (signature == null) { if (requireSignature) { throw samlException("Logout request is not signed"); } } else { validateSignature(signature); } checkIssuer(logoutRequest.getIssuer(), logoutRequest); checkDestination(logoutRequest); validateNotOnOrAfter(logoutRequest.getNotOnOrAfter()); return new Result(logoutRequest.getID(), SamlNameId.fromXml(getNameID(logoutRequest)), getSessionIndex(logoutRequest), relayState); } private NameID getNameID(LogoutRequest logoutRequest) { final NameID nameID = logoutRequest.getNameID(); if (nameID == null) { final EncryptedID encryptedID = logoutRequest.getEncryptedID(); if (encryptedID != null) { final SAMLObject samlObject = decrypt(encryptedID); if (samlObject instanceof NameID) { return (NameID) samlObject; } } } return nameID; } private SAMLObject decrypt(EncryptedID encrypted) { if (decrypter == null) { throw samlException("SAML EncryptedID [" + text(encrypted, 32) + "] is encrypted, but no decryption key is available"); } try { return decrypter.decrypt(encrypted); } catch (DecryptionException e) { logger.debug( () -> format( "Failed to decrypt SAML EncryptedID [%s] with [%s]", text(encrypted, 512), describe(getSpConfiguration().getEncryptionCredentials()) ), e ); throw samlException("Failed to decrypt SAML EncryptedID " + text(encrypted, 32), e); } } private static String getSessionIndex(LogoutRequest logoutRequest) { return logoutRequest.getSessionIndexes().stream().map(as -> as.getValue()).filter(Objects::nonNull).findFirst().orElse(null); } private void checkDestination(LogoutRequest request) { final String url = getSpConfiguration().getLogoutUrl(); if (url == null) { throw samlException( "SAML request " + request.getID() + " is for destination " + request.getDestination() + " but this realm is not configured for logout" ); } if (url.equals(request.getDestination()) == false) { throw samlException( "SAML request " + request.getID() + " is for destination " + request.getDestination() + " but this realm uses " + url ); } } public static class Result { private final String requestId; private final SamlNameId nameId; private final String session; private final String relayState; public Result(String requestId, SamlNameId nameId, String session, String relayState) { this.requestId = requestId; this.nameId = nameId; this.session = session; this.relayState = relayState; } public String getRequestId() { return requestId; } public SamlNameId getNameId() { return nameId; } public String getSession() { return session; } public String getRelayState() { return relayState; } @Override public String toString() { return "SamlLogoutRequestHandler.Result{" + "requestId='" + requestId + '\'' + ", nameId=" + nameId + ", session='" + session + '\'' + ", relayState='" + relayState + '\'' + '}'; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy