From dbe050ac5bce1d3f87114cb64e37d6b111c01d78 Mon Sep 17 00:00:00 2001 From: gurtzoo Date: Wed, 6 Sep 2023 09:56:06 -0400 Subject: [PATCH] IDP Initiate POST SLO Implementation --- README.md | 3 + auth_mellon_handler.c | 163 ++++++++++++++++++++++++-- auth_mellon_util.c | 1 + doc/user_guide/mellon_user_guide.adoc | 80 +++++++++---- 4 files changed, 215 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ada106f..afa5697 100644 --- a/README.md +++ b/README.md @@ -658,6 +658,9 @@ The following is an example of metadata for the example configuration: + urn:oasis:names:tc:SAML:2.0:nameid-format:transient diff --git a/auth_mellon_handler.c b/auth_mellon_handler.c index 3e0bf57..50573ff 100644 --- a/auth_mellon_handler.c +++ b/auth_mellon_handler.c @@ -190,6 +190,9 @@ static char *am_generate_metadata(apr_pool_t *p, request_rec *r) \n\ + \n\ urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n\ msg_url); + message = am_htmlencode(r, profile->msg_body); + if (profile->msg_relayState != NULL) { + relay_state = am_htmlencode(r, profile->msg_relayState); + } else { + /* Try to get relayState from post_data (lasso seems not to initialize relayState from post logout) */ + int rc = 0; + relay_state = am_extract_query_parameter(r->pool, post_data, + "RelayState"); + if (relay_state) { + rc = am_urldecode(relay_state); + if (rc != OK) { + AM_LOG_RERROR(APLOG_MARK, APLOG_ERR, rc, r, + "Could not urldecode RelayState value."); + relay_state = NULL; + } + } + } + + if (relay_state) { + output = apr_psprintf(r->pool, + "\n" + "\n" + " \n" + " \n" + " POST data\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "\n", + url, message, relay_state); + } else { + output = apr_psprintf(r->pool, + "\n" + "\n" + " \n" + " \n" + " POST data\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + "\n", + url, message); + } + + ap_set_content_type(r, "text/html"); + ap_rputs(output, r); + + return OK; +} /* Returns a SAML response * * Parameters: * request_rec *r The current request. * LassoProfile *profile The profile object. + * char *post_data The post data (if present for POST logout) * * Returns: * HTTP_INTERNAL_SERVER_ERROR if an error occurs, HTTP_SEE_OTHER for the * Redirect binding and OK for the SOAP binding. */ static int am_return_logout_response(request_rec *r, - LassoProfile *profile) + LassoProfile *profile, + char *post_data) { if (profile->msg_url && profile->msg_body) { /* POST binding response */ - AM_LOG_RERROR(APLOG_MARK, APLOG_ERR, 0, r, - "Error building logout response message." - " POST binding is unsupported."); - return HTTP_INTERNAL_SERVER_ERROR; + return am_set_logout_response_post_content(r, profile, post_data); } else if (profile->msg_url) { /* HTTP-Redirect binding response */ apr_table_setn(r->headers_out, "Location", @@ -655,12 +745,16 @@ static void am_restore_lasso_profile_state(request_rec *r, * request_rec *r The logout request. * LassoLogout *logout A LassoLogout object initiated with * the current session. + * char *msg The logout message to process. + * char *post_data The post data (if present for POST logout) * * Returns: * OK on success, or an error if any of the steps fail. */ static int am_handle_logout_request(request_rec *r, - LassoLogout *logout, char *msg) + LassoLogout *logout, + char *msg, + char *post_data) { gint res = 0, rc = HTTP_OK; am_cache_entry_t *session = NULL; @@ -753,7 +847,7 @@ static int am_handle_logout_request(request_rec *r, rc = HTTP_INTERNAL_SERVER_ERROR; goto exit; } - rc = am_return_logout_response(r, &logout->parent); + rc = am_return_logout_response(r, &logout->parent, post_data); exit: if (session != NULL) { @@ -764,6 +858,43 @@ static int am_handle_logout_request(request_rec *r, return rc; } +/* This function handles an IdP initiated logout request HTTP-POST. + * + * Parameters: + * request_rec *r The logout request. + * LassoLogout *logout A LassoLogout object initiated with + * the current session. + * char *post_data The post data (if present for POST logout) + * + * Returns: + * OK on success, or an error if any of the steps fail. + */ +static int am_handle_logout_request_POST(request_rec *r, + LassoLogout *logout, + char *post_data) +{ + int rc = 0; + char *saml_request; + + saml_request = am_extract_query_parameter(r->pool, post_data, + "SAMLRequest"); + + if (saml_request == NULL) { + AM_LOG_RERROR(APLOG_MARK, APLOG_ERR, rc, r, + "Could not find SAMLRequest field in POST data."); + return HTTP_BAD_REQUEST; + } + + rc = am_urldecode(saml_request); + if (rc != OK) { + AM_LOG_RERROR(APLOG_MARK, APLOG_ERR, rc, r, + "Could not urldecode SAMLRequest value."); + return rc; + } + + return am_handle_logout_request(r, logout, saml_request, post_data); +} + /* This function handles an invalidate request. * * Parameters: @@ -1169,7 +1300,7 @@ static int am_handle_logout(request_rec *r) /* * First check for POST method, which could be either IdP-initiated SOAP - * logout request or POST logout response from IdP. + * or HTTP-POST logout request or POST logout response from IdP. */ if ((r->args == NULL) && (r->method_number == M_POST)) { int rc; @@ -1187,14 +1318,22 @@ static int am_handle_logout(request_rec *r) } if (content_type != NULL && - am_has_header(r, content_type, "application/x-www-form-urlencoded")) + am_has_header(r, content_type, "application/x-www-form-urlencoded")) { + /* POST can be for a SAMLRequest (IDP request for SLO) or a SAMLResponse (IDP response for SLO) */ + char *saml_param; + saml_param = am_extract_query_parameter(r->pool, post_data, + "SAMLResponse"); + if (saml_param) { return am_handle_logout_response_POST(r, logout, post_data); - else - return am_handle_logout_request(r, logout, post_data); + } + } + + /* HTTP-POST Request */ + return am_handle_logout_request_POST(r, logout, post_data); } else if(am_extract_query_parameter(r->pool, r->args, "SAMLRequest") != NULL) { /* SAMLRequest - logout request from the IdP. */ - return am_handle_logout_request(r, logout, r->args); + return am_handle_logout_request(r, logout, r->args, r->args); } else if(am_extract_query_parameter(r->pool, r->args, "SAMLResponse") != NULL) { diff --git a/auth_mellon_util.c b/auth_mellon_util.c index 6a686db..0dcf124 100644 --- a/auth_mellon_util.c +++ b/auth_mellon_util.c @@ -2451,6 +2451,7 @@ int am_get_boolean_query_parameter(request_rec *r, const char *name, * "AuthnRequestsSigned" * "AssertionConsumerService PAOS 2" * "SingleLogoutService HTTP-Redirect" + * "SingleLogoutService HTTP-POST" * "SingleLogoutService SOAP" * "AssertionConsumerService HTTP-Artifact 1" * "NameIDFormat" diff --git a/doc/user_guide/mellon_user_guide.adoc b/doc/user_guide/mellon_user_guide.adoc index ff050e6..296c21e 100644 --- a/doc/user_guide/mellon_user_guide.adoc +++ b/doc/user_guide/mellon_user_guide.adoc @@ -1105,17 +1105,20 @@ our example authentication. Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://mellon.example.com/mellon/logout" /> - urn:oasis:names:tc:SAML:2.0:nameid-format:transient - + + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + Location="https://mellon.example.com/mellon/logout" /> + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + index="0" - isDefault="true" + isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://mellon.example.com/mellon/postResponse" /> - + index="1" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://mellon.example.com/mellon/artifactResponse" /> - + index="2" Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://mellon.example.com/mellon/paosResponse" /> @@ -1173,21 +1176,24 @@ this URL location. <10> SAML endpoint. Logout messages using the HTTP-Redirect binding are sent to this URL location. -<11> Zero or more `` elements enumerate the name +<11> SAML endpoint. Logout messages using the HTTP-POST binding +are sent to this URL location. + +<12> Zero or more `` elements enumerate the name identifier formats supported by this entity. See <> for details. -<12> [[sp_metadata_acs]] SAML endpoint. Assertions using the HTTP-POST binding are +<13> [[sp_metadata_acs]] SAML endpoint. Assertions using the HTTP-POST binding are delivered to this URL location. -<13> For indexed endpoints if `isDefault` is true then this is the +<14> For indexed endpoints if `isDefault` is true then this is the default endpoint to select. If no endpoint claims to be the default then the first endpoint in the list is the default. -<14> SAML endpoint. Assertions using the HTTP-Artifact binding are +<15> SAML endpoint. Assertions using the HTTP-Artifact binding are delivered to this URL location. -<15> SAML endpoint. Assertions using the PAOS binding are delivered to +<16> SAML endpoint. Assertions using the PAOS binding are delivered to this URL location. PAOS is used the the Enhanced Client or Proxy Profile (a.k.a. ECP). @@ -1222,17 +1228,20 @@ authentication. Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://rhsso.example.com:8443/auth/realms/test/protocol/saml" /> - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent - urn:oasis:names:tc:SAML:2.0:nameid-format:transient - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://rhsso.example.com:8443/auth/realms/test/protocol/saml" /> + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress - Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://rhsso.example.com:8443/auth/realms/test/protocol/saml" /> + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + Location="https://rhsso.example.com:8443/auth/realms/test/protocol/saml" /> + Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://rhsso.example.com:8443/auth/realms/test/protocol/saml" /> @@ -1281,19 +1290,22 @@ sent to this URL location. <9> SAML endpoint. Logout messages using the HTTP-Redirect binding are sent to this URL location. -<10> Zero or more `` elements enumerate the name +<10> SAML endpoint. Logout messages using the HTTP-POST binding are +sent to this URL location. + +<11> Zero or more `` elements enumerate the name identifier formats supported by this entity. See <> for more details. -<11> SAML endpoint. `` messages using the HTTP-POST +<12> SAML endpoint. `` messages using the HTTP-POST binding are sent by the SP to this URL location to establish a Single Sign-On Session. -<12> SAML endpoint. `` messages using the HTTP-Redirect +<13> SAML endpoint. `` messages using the HTTP-Redirect binding are sent by the SP to this URL location to establish a Single Sign-On Session. -<13> SAML endpoint. `` messages using the SOAP +<14> SAML endpoint. `` messages using the SOAP binding are sent by the SP to this URL location to establish a Single Sign-On Session. @@ -1687,6 +1699,7 @@ Host: mellon.example.com Endpoints: SingleLogoutService (SOAP): https://mellon.example.com/mellon/logout SingleLogoutService (HTTP-Redirect): https://mellon.example.com/mellon/logout +SingleLogoutService (HTTP-POST): https://mellon.example.com/mellon/logout AssertionConsumerService (HTTP-POST): https://mellon.example.com/mellon/postResponse AssertionConsumerService (HTTP-Artifact): https://mellon.example.com/mellon/artifactResponse AssertionConsumerService (PAOS): https://mellon.example.com/mellon/paosResponse @@ -3075,6 +3088,9 @@ Service `Location` declarations in your SP metadata. See + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + urn:oasis:names:tc:SAML:2.0:nameid-format:transient