diff --git a/docs/reference/lasso/lasso-sections.txt b/docs/reference/lasso/lasso-sections.txt
index c849b303..4d822505 100644
--- a/docs/reference/lasso/lasso-sections.txt
+++ b/docs/reference/lasso/lasso-sections.txt
@@ -5757,6 +5757,12 @@ LASSO_IDWSF2_DSTREF_DATA_RESPONSE_GET_CLASS
ecp
LassoEcp
LassoEcp
+lasso_ecp_is_provider_in_sp_idplist
+lasso_ecp_is_idp_entry_known_idp_supporting_ecp
+lasso_ecp_set_known_sp_provided_idp_entries_supporting_ecp
+lasso_ecp_has_sp_idplist
+lasso_ecp_get_endpoint_url_by_entity_id
+lasso_ecp_process_sp_idp_list
lasso_ecp_new
lasso_ecp_process_authn_request_msg
lasso_ecp_process_response_msg
diff --git a/lasso/saml-2.0/ecp.c b/lasso/saml-2.0/ecp.c
index 1bd6bf06..ddece31a 100644
--- a/lasso/saml-2.0/ecp.c
+++ b/lasso/saml-2.0/ecp.c
@@ -21,20 +21,74 @@
* along with this program; if not, see .
*/
+/*
+ * SAML2 Profile for ECP (Section 4.2) defines these steps for an ECP
+ * transaction
+ *
+ * 1. ECP issues HTTP Request to SP
+ * 2. SP issues to ECP using PAOS
+ * 3. ECP determines IdP
+ * 4. ECP conveys to IdP using SOAP
+ * 5. IdP identifies principal
+ * 6. IdP issues to ECP, targeted at SP using SOAP
+ * 7. ECP conveys to SP using PAOS
+ * 8. SP grants or denies access to principal
+ */
+
/**
* SECTION:ecp
* @short_description: Enhanced Client or Proxy Profile (SAMLv2)
*
+ * # Introduction
+ *
+ * The #LassoEcp object is used to implement a SAMLv2 ECP client.
+ * If you want to support ECP in a SP see [ecp-sp].
+ * If you want to support ECP in a IdP see [ecp-idp].
+ *
+ * # ECP Operational Steps
+ *
+ * SAML2 Profile for ECP (Section 4.2) defines these steps for an ECP
+ * transaction
+ *
+ * 1. ECP issues HTTP Request to SP
+ * 2. SP issues <samlp:AuthnRequest> to ECP using PAOS
+ * 3. ECP determines IdP
+ * 4. ECP conveys <samlp:AuthnRequest> to IdP using SOAP
+ * 5. IdP identifies principal
+ * 6. IdP issues <samlp:Response> to ECP, targeted at SP using SOAP
+ * 7. ECP conveys <samlp:Response> to SP using PAOS
+ * 8. SP grants or denies access to principal
+ *
+ *
+ *
+ *
**/
+/**
+ * SECTION:ecp-sp
+ * @short_description: How to support ECP in an SP
+ *
+ *
+ * |[
+ * login = lasso_login_new(server);
+ * ]|
+ */
+
+/**
+ * SECTION:ecp-idp
+ * @short_description: How to support ECP in an IdP
+ *
+ *
+ * |[
+ * login = lasso_login_new(server);
+ * ]|
+ */
+
#include "../xml/private.h"
#include
#include
-#include "providerprivate.h"
#include "profileprivate.h"
-#include "../id-ff/providerprivate.h"
-#include "../id-ff/identityprivate.h"
#include "../id-ff/serverprivate.h"
#include "ecpprivate.h"
@@ -42,6 +96,33 @@
#include "ecp.h"
#include "../utils.h"
+#include "../xml/soap-1.1/soap_envelope.h"
+#include "../xml/soap-1.1/soap_header.h"
+#include "../xml/soap-1.1/soap_body.h"
+#include "../xml/soap-1.1/soap_fault.h"
+#include "../xml/misc_text_node.h"
+#include "../xml/paos_request.h"
+#include "../xml/paos_response.h"
+#include "../xml/ecp/ecp_request.h"
+#include "../xml/ecp/ecp_response.h"
+#include "../xml/ecp/ecp_relaystate.h"
+#include "../xml/lib_authn_request.h"
+#include "../xml/saml-2.0/samlp2_response.h"
+#include "../xml/saml-2.0/samlp2_authn_request.h"
+
+/*****************************************************************************/
+/* Prototypes */
+/*****************************************************************************/
+
+static gboolean
+is_provider_in_sp_idplist(GList *idp_list, const gchar *entity_id);
+
+static gboolean
+is_idp_entry_in_entity_id_list(GList *entity_id_list, const LassoSamlp2IDPEntry *idp_entry);
+
+static GList *
+intersect_sp_idplist_with_entity_id_list(GList *sp_provided_idp_entries, GList *known_idp_entity_ids_supporting_ecp);
+
/*****************************************************************************/
/* public methods */
/*****************************************************************************/
@@ -59,12 +140,259 @@ lasso_ecp_destroy(LassoEcp *ecp)
lasso_node_destroy(LASSO_NODE(ecp));
}
+/**
+ * lasso_ecp_is_provider_in_sp_idplist:
+ * @ecp: a #LassoEcp
+ * @entity_id: EntityID to check if member of #LassoEcp.IDPList
+ *
+ * Check to see if the provider with @entity_id is in the
+ * ecp IDPList returned by the SP.
+ *
+ * Return value: TRUE if @entity_id is in #LassoEcp.IDPList, FALSE otherwise
+ */
+gboolean
+lasso_ecp_is_provider_in_sp_idplist(LassoEcp *ecp, const gchar *entity_id) {
+ return is_provider_in_sp_idplist(ecp->sp_idp_list->IDPEntry, entity_id);
+}
+
+/**
+ * lasso_ecp_is_idp_entry_known_idp_supporting_ecp:
+ * @ecp: a #LassoEcp
+ * @idp_entry: #LassoSamlp2IDPEntry to check if member of @entity_id_list
+ *
+ * Check to see if the @idp_entry is in the @entity_id_list
+ *
+ *
+ * Return value: TRUE if @entity_id is in @idp_list, FALSE otherwise
+ */
+gboolean
+lasso_ecp_is_idp_entry_known_idp_supporting_ecp(LassoEcp *ecp, const LassoSamlp2IDPEntry *idp_entry) {
+ return is_idp_entry_in_entity_id_list(ecp->known_idp_entity_ids_supporting_ecp, idp_entry);
+}
+
+/**
+ * lasso_ecp_set_known_sp_provided_idp_entries_supporting_ecp:
+ * @ecp: a #LassoEcp
+ *
+ * The SP may provide a list of #LassoSamlp2IDPEntry
+ * (#LassoEcp.sp_idp_list) which it trusts. The ECP client
+ * has a list of IDP EntityID's it knows supports ECP
+ * (#LassoEcp.known_idp_entity_ids_supporting_ecp). The set of
+ * possible IDP's which can service the SP's authn request are the
+ * interesection of these two lists (the IDP's the SP approves and
+ * IDP's the ECP knows about). This find the common members between
+ * the two lists and assign them to
+ * #LassoEcp.known_sp_provided_idp_entries_supporting_ecp.
+ */
+void
+lasso_ecp_set_known_sp_provided_idp_entries_supporting_ecp(LassoEcp *ecp)
+{
+ lasso_assign_new_list_of_strings(ecp->known_sp_provided_idp_entries_supporting_ecp,
+ intersect_sp_idplist_with_entity_id_list(ecp->sp_idp_list ? ecp->sp_idp_list->IDPEntry : NULL,
+ ecp->known_idp_entity_ids_supporting_ecp));
+}
+
+/**
+ * lasso_ecp_has_sp_idplist:
+ * @ecp: a #LassoEcp
+ *
+ * Returns TRUE if the SP provided an IDP List, FALSE otherwise.
+ */
+gboolean
+lasso_ecp_has_sp_idplist(LassoEcp *ecp)
+{
+ return ecp->sp_idp_list && ecp->sp_idp_list->IDPEntry != NULL;
+}
+
+/**
+ * lasso_ecp_get_endpoint_url_by_entity_id:
+ * @ecp: a #LassoEcp
+ * @entity_id: the EntityID of the IdP
+ *
+ * Returns the SingleSignOnService SOAP endpoint URL for the specified
+ * @entity_id. If the provider cannot be found or if the provider does
+ * not have a matching endpoint NULL will be returned.
+ *
+ * Returns: url (must be freed by caller)
+ */
+gchar *
+lasso_ecp_get_endpoint_url_by_entity_id(LassoEcp *ecp, const gchar *entity_id)
+{
+ LassoProfile *profile;
+
+ profile = LASSO_PROFILE(ecp);
+
+ return lasso_server_get_endpoint_url_by_id(profile->server, entity_id,
+ "SingleSignOnService SOAP");
+}
+
+/**
+ * lasso_ecp_process_sp_idp_list:
+ * @ecp: a #LassoEcp
+ *
+ * The SP may optionally send a list of IdP's it trusts in ecp:IDPList.
+ * The ecp:IDPList may not be complete if the IDPList.GetComplete is
+ * non-NULL. If so the IDPList.GetComplete is a URL where a complete
+ * IDPList may be fetched.
+ *
+ * Whenever the IDPList is updated this function needs to be called
+ * because it sets the
+ * #LassoEcp.known_sp_provided_idp_entries_supporting_ecp and the
+ * default IdP URL (#LassoProfile.msg_url).
+ *
+ * The #LassoEcp client has a list of IdP's it knows supports ECP
+ * (#LassoEcp.known_idp_entity_ids_supporting_ecp). The set of IdP's
+ * available to select from should be those in common between SP
+ * provided IdP list and those known by this ECP client to support
+ * ECP.
+ *
+ * This routine sets the
+ * #LassoEcp.known_sp_provided_idp_entries_supporting_ecp list to the
+ * common members (e.g. intersection) of the SP provided IdP list and
+ * the list of known IdP's supporting ECP.
+ *
+ * A default IdP will be selected and it's endpoint URL will be
+ * assigned to #LassoProfile.msg_url.
+ *
+ * If the SP provided an IDP list then the default URL will be taken
+ * from first IDPEntry in
+ * #LassoEcp.known_sp_provided_idp_entries_supporting_ecp otherwise
+ * it will be taken from #LassoEcp.known_idp_entity_ids_supporting_ecp.
+ *
+ */
+int
+lasso_ecp_process_sp_idp_list(LassoEcp *ecp, const LassoSamlp2IDPList *sp_idp_list)
+{
+ int rc = 0;
+ LassoProfile *profile;
+ gchar *provider_id = NULL;
+ gchar *url;
+
+ profile = LASSO_PROFILE(ecp);
+
+ lasso_assign_gobject(ecp->sp_idp_list, sp_idp_list);
+
+ /* Build a list of IdP's which are common between the SP and those we know support ECP */
+ lasso_ecp_set_known_sp_provided_idp_entries_supporting_ecp(ecp);
+
+ /* Select a default IdP */
+ provider_id = NULL;
+ if (lasso_ecp_has_sp_idplist(ecp)) {
+ /* Select first IDP provided by SP that is in our IDP list */
+ if (ecp->known_sp_provided_idp_entries_supporting_ecp) {
+ provider_id = ((LassoSamlp2IDPEntry*)ecp->known_sp_provided_idp_entries_supporting_ecp->data)->ProviderID;
+ }
+ }
+ if (!provider_id) {
+ /* Select first IDP from our IDP list */
+ if (ecp->known_idp_entity_ids_supporting_ecp) {
+ provider_id = ecp->known_idp_entity_ids_supporting_ecp->data;
+ }
+ }
+
+ /* If we have a default IdP assign it's ECP URL to the profile->msg_url */
+ lasso_release_string(profile->msg_url)
+ if (provider_id) {
+ url = lasso_ecp_get_endpoint_url_by_entity_id(ecp, provider_id);
+ lasso_assign_new_string(profile->msg_url, url);
+ }
+ return rc;
+}
+
/*****************************************************************************/
/* private methods */
/*****************************************************************************/
static LassoNodeClass *parent_class = NULL;
+/**
+ * compare_idp_entry_to_entity_id:
+ *
+ * Helper function for is_provider_in_sp_idplist().
+ */
+static gboolean
+compare_idp_entry_to_entity_id(gconstpointer a, gconstpointer b)
+{
+ const LassoSamlp2IDPEntry *idp_entry = LASSO_SAMLP2_IDP_ENTRY(a);
+ const gchar *entity_id = b;
+
+ return g_strcmp0(idp_entry->ProviderID, entity_id);
+}
+
+/**
+ * is_provider_in_sp_idplist:
+ * @idp_list: GList of LassoSamlp2IDPEntry
+ * @entity_id: EntityID to check if member of @idp_list
+ *
+ * Check if the provider with @entity_id is in the #idp_list.
+ *
+ * Return value: TRUE if @entity_id is in @idp_list, FALSE otherwise
+ */
+static gboolean
+is_provider_in_sp_idplist(GList *idp_list, const gchar *entity_id) {
+ return g_list_find_custom(idp_list, entity_id, compare_idp_entry_to_entity_id) == NULL ? FALSE : TRUE;
+}
+
+/**
+ * compare_entity_id_to_idp_entry:
+ *
+ * Helper function for is_idp_entry_in_entity_id_list().
+ */
+static gboolean
+compare_entity_id_to_idp_entry(gconstpointer a, gconstpointer b)
+{
+ const gchar *entity_id = a;
+ const LassoSamlp2IDPEntry *idp_entry = LASSO_SAMLP2_IDP_ENTRY(b);
+
+ return g_strcmp0(entity_id, idp_entry->ProviderID);
+}
+
+/**
+ * is_idp_entry_in_entity_id_list:
+ * @entity_id_list: #GList of entity id's
+ * @idp_entry: #LassoSamlp2IDPEntry to check if member of @entity_id_list
+ *
+ * Check if the provider with @entity_id is in the #idp_list.
+ *
+ * Return value: TRUE if @entity_id is in @idp_list, FALSE otherwise
+ */
+static gboolean
+is_idp_entry_in_entity_id_list(GList *entity_id_list, const LassoSamlp2IDPEntry *idp_entry) {
+ return g_list_find_custom(entity_id_list, idp_entry, compare_entity_id_to_idp_entry) == NULL ? FALSE : TRUE;
+}
+
+/*
+ * intersect_sp_idplist_with_entity_id_list:
+ * @sp_provided_idp_entries: #GList of #LassoSamlp2IDPEntry
+ * @known_idp_entity_ids_supporting_ecp: #GList of entity id's
+ *
+ * The SP may provide a list of #LassoSamlp2IDPEntry which it
+ * trusts. The ECP client has a list of IDP EntityID's it knows
+ * supports ECP. The set of possible IDP's which can service the SP's
+ * authn request are the interesection of these two lists (the IDP's
+ * the SP approves and IDP's the ECP knows about). This function
+ * accepts the SP's IDPEntry list and returns a new list containing
+ * only those the ECP client knows about. The returned list must be
+ * freed with lasso_release_list_of_gobjects().
+ *
+ * Return value: GList of #LassoSamlp2IDPEntry
+ * (caller must free with lasso_release_list_of_gobjects())
+ */
+static GList *
+intersect_sp_idplist_with_entity_id_list(GList *sp_provided_idp_entries, GList *known_idp_entity_ids_supporting_ecp)
+{
+ GList *i;
+ GList *new_list = NULL;
+
+ lasso_foreach(i, sp_provided_idp_entries) {
+ LassoSamlp2IDPEntry *idp_entry = i->data;
+ if (is_idp_entry_in_entity_id_list(known_idp_entity_ids_supporting_ecp, idp_entry)) {
+ lasso_list_add_gobject(new_list, idp_entry);
+ }
+ }
+ return new_list;
+}
+
/*****************************************************************************/
/* overridden parent class methods */
/*****************************************************************************/
@@ -74,15 +402,20 @@ dispose(GObject *object)
{
LassoEcp *ecp = LASSO_ECP(object);
- if (ecp->private_data->messageID) {
- xmlFree(ecp->private_data->messageID);
- ecp->private_data->messageID = NULL;
+ if (ecp->private_data->dispose_has_run) {
+ return;
}
+ ecp->private_data->dispose_has_run = TRUE;
- if (ecp->private_data->relay_state) {
- xmlFree(ecp->private_data->relay_state);
- ecp->private_data->relay_state = NULL;
- }
+ lasso_release_string(ecp->assertion_consumer_url);
+ lasso_release_string(ecp->message_id);
+ lasso_release_string(ecp->response_consumer_url);
+ lasso_release_string(ecp->relaystate);
+ lasso_release_gobject(ecp->issuer);
+ lasso_release_string(ecp->provider_name);
+ lasso_release_gobject(ecp->sp_idp_list);
+ lasso_release_list_of_gobjects(ecp->known_sp_provided_idp_entries_supporting_ecp);
+ lasso_release_list_of_strings(ecp->known_idp_entity_ids_supporting_ecp);
G_OBJECT_CLASS(parent_class)->dispose(G_OBJECT(ecp));
}
@@ -92,7 +425,6 @@ finalize(GObject *object)
{
LassoEcp *ecp = LASSO_ECP(object);
lasso_release(ecp->private_data);
- ecp->private_data = NULL;
G_OBJECT_CLASS(parent_class)->finalize(object);
}
@@ -105,10 +437,6 @@ static void
instance_init(LassoEcp *ecp)
{
ecp->private_data = g_new0(LassoEcpPrivate, 1);
- ecp->private_data->messageID = NULL;
- ecp->private_data->relay_state = NULL;
-
- ecp->assertionConsumerURL = NULL;
}
static void
@@ -123,151 +451,413 @@ class_init(LassoEcpClass *klass)
G_OBJECT_CLASS(klass)->dispose = dispose;
G_OBJECT_CLASS(klass)->finalize = finalize;
}
-
+/**
+ * lasso_ecp_process_authn_request_msg:
+ * @ecp: this #LassoEcp object
+ * @authn_request_msg: the PAOS authn request received from the SP
+ *
+ * This function implements the following ECP step:
+ * ECP Step 3, ECP determines IdP
+ * ECP Step 4, parse SP PAOS Authn request, build SOAP for IdP
+ *
+ * This is to be used in an ECP client. The @authn_request_msg is the
+ * SOAP PAOS message received from the SP in response to a resource
+ * request with an HTTP Accept header indicating PAOS support.
+ *
+ * The following actions are implemented:
+ *
+ * * Extract the samlp:AuthnRequest from the SOAP body and build a
+ * new SOAP message containing the samlp:AuthnRequest which will
+ * be forwarded to the IdP. This new SOAP message is stored in the
+ * #LassoProfile.msg_body.
+ *
+ * * Parse the SOAP header which will contain a paos:Request, a
+ * ecp:Request and optionally a ecp:RelayState. Some of the data
+ * in these headers need to be preserved for later processing steps.
+ *
+ * 1. The paos:Request.responseConsumerURL is copied to the
+ * #LassoEcp.response_consumer_url. This is necessary because the
+ * ECP client MUST assure it matches the
+ * ecp:Response.AssertionConsumerServiceURL returned by the IdP to
+ * prevent man-in-the-middle attacks. It must also match the
+ * samlp:AuthnRequest.AssertionConsumerServiceURL.
+ *
+ * 2. If the paos:Request contained a messageID it is copied to
+ * #LassoEcp.message_id so it can be returned in the subsequent
+ * paos:Response.refToMessageID. This allows a provider to
+ * correlate messages.
+ *
+ * 3. If an ecp:RelayState is present it is copied to
+ * #LassoEcp.relaystate. This is necessary because in step 7 when
+ * the ECP responds to the SP it must include RelayState provided in
+ * the request.
+ *
+ * * In addition the following items are copied to the #LassoEcp for
+ * informational purposes:
+ *
+ * * #LassoEcp.issuer = ecp:Request.Issuer
+ *
+ * * #LassoEcp.provider_name = ecp:Request.ProviderName
+ *
+ * * #LassoEcp.is_passive = ecp:Request.IsPassive
+ *
+ * * #LassoEcp.sp_idp_list = ecp:Request.IDPList
+ *
+ * # IdP Selection
+ *
+ * In Step 3. The ECP must determine the IdP to forward the
+ * AuthnRequest to. There are two sets of IdP's which come into
+ * play. The ECP client has a set of IdP's it knows about because
+ * their metadata has been loaded into the #LassoServer object. The SP
+ * may optionally send a list of IdP's in the ecp:Request that it
+ * trusts.
+ *
+ * The selected IdP *must* be one of the IdP's loaded into the
+ * #LassoServer object from metadata because the IdP endpoints must be
+ * known. Furthermore the IdP *must* support the SingleSignOnService
+ * using the SOAP binding. Therefore the known IdP's are filtered for
+ * those that match this criteria and a list of their EntityID's are
+ * assigned to #LassoEcp.known_idp_entity_ids_supporting_ecp. The
+ * selected IdP *must* be a member of this list.
+ *
+ * The SP may optionally send a list of IdP's it trusts. If the SP
+ * sends an IDPList the selected IdP should be a member of this list
+ * and from above we know it must also be a member of the
+ * #LassoEcp.known_idp_entity_ids_supporting_ecp. Therefore the
+ * #LassoEcp.known_sp_provided_idp_entries_supporting_ecp list is set
+ * to the common members (e.g. intersection) of the SP provided IdP
+ * list and the list of known IdP's supporting ECP.
+ *
+ * When making an IdP selection if the SP provided an IdP List (use
+ * #LassoEcp.lasso_ecp_has_sp_idplist()) then it should be selected
+ * from the #LassoEcp.known_sp_provided_idp_entries_supporting_ecp
+ * list. Otherwise the IdP should be selected from
+ * #LassoEcp.known_idp_entity_ids_supporting_ecp.
+ *
+ * A default IdP will be selected using the above logic by picking the
+ * first IdP in the appropriate list, it's endpoint URL will be
+ * assigned to #LassoProfile.msg_url. The above processing is
+ * implemented by #LassoEcp.lasso_ecp_process_sp_idp_list() and if the
+ * SP IDPList is updated this routine should be called.
+ *
+ * A note about the 3 IdP lists. The #LassoEcp.sp_idp_list.IDPList
+ * and #LassoEcp.known_sp_provided_idp_entries_supporting_ecp are
+ * #GList's of #LassoSamlp2IDPEntry object which have a ProviderID,
+ * Name, and Loc attribute. You may wish to use this SP provided
+ * information when making a decision or presenting in a user
+ * interface that allows a user to make a choice. The
+ * #LassoEcp.known_idp_entity_ids_supporting_ecp is a #GList of
+ * EntityID strings.
+ *
+ * Given the EntityID of an IdP you can get the ECP endpoint by
+ * calling #LassoEcp.lasso_ecp_get_endpoint_url_by_entity_id()
+ *
+ * # Results
+ *
+ * After a successful return from this call you are ready to complete
+ * Step 4. and forward the request the IdP.
+ *
+ * The URL to send to the request to will be #LassoProfile.msg_url (if
+ * you accept the default IdP) and the body of the message to post
+ * will be #LassoProfile.msg_body.
+ *
+ *
+ * # Side Effects
+ *
+ * After a successful return the #LassoEcp object will be updated with:
+ *
+ * * ecp->response_consumer_url = paos_request->responseConsumerURL
+ * * ecp->message_id = paos_request->messageID
+ * * ecp->relaystate = ecp_relaystate->RelayState
+ * * ecp->issuer = ecp_request->Issue
+ * * ecp->provider_name = ecp_request->ProviderName
+ * * ecp->is_passive = ecp_request->IsPassive
+ * * ecp->known_idp_entity_ids_supporting_ecp
+ * * ecp->sp_idp_list = ecp_request->IDPList
+ * * ecp->known_sp_provided_idp_entries_supporting_ecp
+ *
+ */
int
lasso_ecp_process_authn_request_msg(LassoEcp *ecp, const char *authn_request_msg)
{
- xmlDoc *doc;
- xmlXPathContext *xpathCtx;
- xmlXPathObject *xpathObj;
- xmlNode *xmlnode;
+ int rc = 0;
+ LassoSoapEnvelope *envelope = NULL;
+ LassoSoapHeader *header = NULL;
+ LassoSoapBody *body = NULL;
+ LassoPaosRequest *paos_request = NULL;
+ LassoEcpRequest *ecp_request = NULL;
+ LassoEcpRelayState *ecp_relaystate = NULL;
+ LassoSamlp2AuthnRequest *authn_request = NULL;
+ GList *i;
LassoProfile *profile;
- LassoProvider *remote_provider;
g_return_val_if_fail(LASSO_IS_ECP(ecp), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ);
g_return_val_if_fail(authn_request_msg != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
profile = LASSO_PROFILE(ecp);
- doc = lasso_xml_parse_memory(authn_request_msg, strlen(authn_request_msg));
- xpathCtx = xmlXPathNewContext(doc);
+ /* Get the SOAP envelope */
+ lasso_extract_node_or_fail(envelope, lasso_soap_envelope_new_from_message(authn_request_msg),
+ SOAP_ENVELOPE, LASSO_PROFILE_ERROR_INVALID_SOAP_MSG);
- xmlXPathRegisterNs(xpathCtx, (xmlChar*)"ecp", (xmlChar*)LASSO_ECP_HREF);
- xpathObj = xmlXPathEvalExpression((xmlChar*)"//ecp:RelayState", xpathCtx);
- if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr) {
- xmlnode = xpathObj->nodesetval->nodeTab[0];
- ecp->private_data->relay_state = xmlNodeGetContent(xmlnode);
- }
- xmlXPathFreeObject(xpathObj);
+ /* Get the SOAP body */
+ lasso_extract_node_or_fail(body, envelope->Body, SOAP_BODY,
+ LASSO_SOAP_ERROR_MISSING_BODY);
+ goto_cleanup_if_fail_with_rc(body->any && LASSO_IS_NODE(body->any->data),
+ LASSO_SOAP_ERROR_MISSING_BODY);
+ lasso_extract_node_or_fail(authn_request, body->any->data, SAMLP2_AUTHN_REQUEST,
+ LASSO_ECP_ERROR_MISSING_AUTHN_REQUEST);
- xmlXPathRegisterNs(xpathCtx, (xmlChar*)"paos", (xmlChar*)LASSO_PAOS_HREF);
- xpathObj = xmlXPathEvalExpression((xmlChar*)"//paos:Request", xpathCtx);
- if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr) {
- ecp->private_data->messageID = xmlGetProp(
- xpathObj->nodesetval->nodeTab[0], (xmlChar*)"messageID");
- }
- xmlXPathFreeObject(xpathObj);
+ /* Get the SOAP header */
+ lasso_extract_node_or_fail(header, envelope->Header, SOAP_HEADER,
+ LASSO_SOAP_ERROR_MISSING_HEADER);
+ goto_cleanup_if_fail_with_rc(header->Other && LASSO_IS_NODE(header->Other->data),
+ LASSO_SOAP_ERROR_MISSING_HEADER);
- xmlXPathRegisterNs(xpathCtx, (xmlChar*)"s", (xmlChar*)LASSO_SOAP_ENV_HREF);
- xpathObj = xmlXPathEvalExpression((xmlChar*)"//s:Header", xpathCtx);
- if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr) {
- xmlnode = xpathObj->nodesetval->nodeTab[0];
- xmlUnlinkNode(xmlnode);
- xmlFreeNode(xmlnode);
- }
- xmlXPathFreeObject(xpathObj);
- xmlXPathFreeContext(xpathCtx);
- xpathCtx = NULL;
- xpathObj = NULL;
+ /*
+ * Get the following header elements:
+ * * paos:Request (required)
+ * * ecp:Request (required)
+ * * ecp:RelayState (optional)
+ */
+ lasso_foreach(i, header->Other) {
+ if (!paos_request && LASSO_IS_PAOS_REQUEST(i->data)) {
+ paos_request = (LassoPaosRequest *)i->data;
+ } else if (!ecp_request && LASSO_IS_ECP_REQUEST(i->data)) {
+ ecp_request = (LassoEcpRequest *)i->data;
+ } else if (!ecp_relaystate && LASSO_IS_ECP_RELAYSTATE(i->data)) {
+ ecp_relaystate = (LassoEcpRelayState *)i->data;
+ }
- xmlnode = xmlDocGetRootElement(doc);
- lasso_assign_new_string(LASSO_PROFILE(ecp)->msg_body,
- lasso_xmlnode_to_string(xmlnode, 0, 0))
- lasso_release_doc(doc);
-
- profile->remote_providerID = lasso_server_get_first_providerID_by_role(profile->server, LASSO_PROVIDER_ROLE_IDP);
- if (profile->remote_providerID == NULL) {
- return critical_error(LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND);
+ if (ecp_relaystate && ecp_request && paos_request) break;
}
- remote_provider = lasso_server_get_provider(profile->server, profile->remote_providerID);
- if (LASSO_IS_PROVIDER(remote_provider) == FALSE) {
- return critical_error(LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND);
+ goto_cleanup_if_fail_with_rc(paos_request, LASSO_PAOS_ERROR_MISSING_REQUEST);
+ goto_cleanup_if_fail_with_rc(ecp_request, LASSO_ECP_ERROR_MISSING_REQUEST);
+
+ /* Copy data for later use */
+ if (paos_request->responseConsumerURL) {
+ lasso_assign_string(ecp->response_consumer_url, paos_request->responseConsumerURL);
+ } else {
+ goto_cleanup_with_rc(LASSO_PAOS_ERROR_MISSING_RESPONSE_CONSUMER_URL);
}
- profile->msg_url = lasso_provider_get_metadata_one(remote_provider,
- "SingleSignOnService SOAP");
- if (profile->msg_url == NULL) {
- return critical_error(LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL);
+ if (paos_request->messageID) {
+ lasso_assign_string(ecp->message_id, paos_request->messageID);
}
- return 0;
+ if (ecp_relaystate) {
+ lasso_assign_string(ecp->relaystate, ecp_relaystate->RelayState);
+ }
+
+ lasso_assign_gobject(ecp->issuer, ecp_request->Issuer);
+ lasso_assign_string(ecp->provider_name, ecp_request->ProviderName);
+ ecp->is_passive = ecp_request->IsPassive;
+
+ /*
+ * Build a SOAP envelope whose body contains the original
+ * AuthnRequest received from the SP. The obvious solution is to
+ * serialize into XML the LassoSamlp2AuthnRequest LassoNode that
+ * was serialized from XML when we parsed the PAOS request
+ * (e.g. lasso_node_export_to_soap(LASSO_NODE(authn_request))) but
+ * that won't work because XML serialization is not symmetric.
+ * Serializing from XML into a LassoNode and then serializing the
+ * LassoNode back into XML does not produce the originial XML
+ * content. This is mostly due to the presence of signatures. In
+ * order to forward the *exact* same XML AuthnRequest we received
+ * from the SP to the IdP we mark the LassoSamlp2AuthnRequest with
+ * a flag indicating it's xmlNode needs to be preserved
+ * (e.g. keep_xmlnode = TRUE). We copy the xmlNode into a special
+ * LassoNode (LassoMiscTextNode) which is capable of preserving
+ * the exact xmlNode thus insuring no modification was made to the
+ * content.
+ *
+ * We assign the SOAP message to the profile->msg_body so it's
+ * available for transmitting to the IdP.
+ */
+
+ {
+ xmlNodePtr xml;
+ LassoMiscTextNode *misc;
+
+ xml = lasso_node_get_original_xmlnode(LASSO_NODE(authn_request));
+
+ misc = lasso_misc_text_node_new_with_xml_node(xml);
+ lasso_assign_new_string(LASSO_PROFILE(ecp)->msg_body,
+ lasso_node_export_to_soap(LASSO_NODE(misc)));
+ lasso_release_gobject(misc);
+ }
+
+
+ /* Set up for IdP selection, build IdP lists, make default IdP choice */
+
+ /* Filter our server's list of IdP's to only include those that support ECP */
+ ecp->known_idp_entity_ids_supporting_ecp = lasso_server_get_filtered_provider_list(
+ profile->server, LASSO_PROVIDER_ROLE_IDP, LASSO_MD_PROTOCOL_TYPE_SINGLE_SIGN_ON,
+ LASSO_HTTP_METHOD_SOAP);
+
+ /* Update the IdP lists and select a default URL */
+ lasso_ecp_process_sp_idp_list(ecp, ecp_request->IDPList);
+
+ cleanup:
+ lasso_release_gobject(envelope);
+
+ return rc;
}
+/**
+ * lasso_ecp_process_response_msg:
+ * @ecp: this #LassoEcp object
+ * @response_msg: the SOAP response from the IdP
+ *
+ *
+ * The function implements ECP Step 7; parse IdP SOAP response and
+ * build PAOS response for SP.
+ *
+ * See SAML Profile Section 4.2.4.5 PAOS Response Header Block: ECP to SP
+ *
+ * This is to be used in an ECP client. The @response_msg parameter
+ * contains the SOAP response from the IdP. We extract the ECP Header
+ * Block and body from it. We will generate a new PAOS message to send
+ * to the SP, the SOAP header will contain a paos:Response. If we
+ * received a paos:Request.MessageID in Step. 4 from the SP then we
+ * will copy it back to the paos:Response.refToMessageID. If we
+ * received a RelayState we will add that to the SOAP header as well.
+ *
+ * To prevent a man-in-the-middle attack we verify the
+ * responseConsumerURL we received in Step 4 matches the
+ * ecp:Response.AssertionConsumerServiceURL we just received back from
+ * the IdP. If they do not match we return a
+ * #LASSO_ECP_ERROR_ASSERTION_CONSUMER_URL_MISMATCH error and set the
+ * #LassoProvider.msg_body to the appropriate SOAP fault.
+ *
+ * The new PAOS message for the SP we are buiding contains the IdP
+ * response in the new SOAP body and the new SOAP headers will contain
+ * a paso:Response and optionally an ecp:RelayState.
+ *
+ * After a successful return from this call you are ready to complete
+ * Step 7. and forward the response to the SP.
+ *
+ * The PASO message is assigned to the #LassoProvider.msg_body and
+ * the desination URL is assigned to the #LassoProvider.msg_url.
+ *
+ * # Side Effects
+ *
+ * After a successful return the #LassoEcp object will be updated with:
+ *
+ * * ecp->assertion_consumer_url = ecp_response->AssertionConsumerServiceURL
+ * * ecp.profile.msg_url = ecp->assertion_consumer_url
+ * * ecp.profile.msg_body_url = PAOS response to SP
+ */
int
lasso_ecp_process_response_msg(LassoEcp *ecp, const char *response_msg)
{
- xmlDoc *doc;
- xmlXPathContext *xpathCtx;
- xmlXPathObject *xpathObj;
- xmlNode *new_envelope, *header, *paos_response, *ecp_relay_state;
- xmlNode *body = NULL;
- xmlNs *soap_env_ns, *ecp_ns;
+ int rc = 0;
+ LassoSoapEnvelope *envelope = NULL;
+ LassoSoapHeader *header = NULL;
+ LassoSoapBody *body = NULL;
+ LassoPaosResponse *paos_response = NULL;
+ LassoEcpResponse *ecp_response = NULL;
+ LassoEcpRelayState *ecp_relaystate = NULL;
+ LassoSamlp2Response *samlp2_response = NULL;
+ GList *i;
+ GList *headers = NULL;
g_return_val_if_fail(LASSO_IS_ECP(ecp), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ);
g_return_val_if_fail(response_msg != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
- doc = lasso_xml_parse_memory(response_msg, strlen(response_msg));
- xpathCtx = xmlXPathNewContext(doc);
- xmlXPathRegisterNs(xpathCtx, (xmlChar*)"s", (xmlChar*)LASSO_SOAP_ENV_HREF);
- xpathObj = xmlXPathEvalExpression((xmlChar*)"//s:Body", xpathCtx);
- if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr) {
- body = xmlCopyNode(xpathObj->nodesetval->nodeTab[0], 1);
- }
- xmlXPathFreeObject(xpathObj);
+ /* Get the SOAP envelope */
+ lasso_extract_node_or_fail(envelope, lasso_soap_envelope_new_from_message(response_msg),
+ SOAP_ENVELOPE, LASSO_PROFILE_ERROR_INVALID_SOAP_MSG);
- xmlXPathRegisterNs(xpathCtx, (xmlChar*)"ecp", (xmlChar*)LASSO_ECP_HREF);
- xpathObj = xmlXPathEvalExpression((xmlChar*)"//ecp:Response", xpathCtx);
- if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr) {
- ecp->assertionConsumerURL = (char*)xmlGetProp(
- xpathObj->nodesetval->nodeTab[0], (xmlChar*)"AssertionConsumerURL");
- }
- xmlXPathFreeObject(xpathObj);
- xmlXPathFreeContext(xpathCtx);
- xpathCtx = NULL;
- xpathObj = NULL;
+ /* Get the SOAP body */
+ lasso_extract_node_or_fail(body, envelope->Body, SOAP_BODY,
+ LASSO_SOAP_ERROR_MISSING_BODY);
+ goto_cleanup_if_fail_with_rc(body->any && LASSO_IS_NODE(body->any->data),
+ LASSO_SOAP_ERROR_MISSING_BODY);
+ lasso_extract_node_or_fail(samlp2_response, body->any->data, SAMLP2_RESPONSE,
+ LASSO_ECP_ERROR_MISSING_SAML_RESPONSE);
- new_envelope = xmlNewNode(NULL, (xmlChar*)"Envelope");
- xmlSetNs(new_envelope, xmlNewNs(new_envelope,
- (xmlChar*)LASSO_SOAP_ENV_HREF, (xmlChar*)LASSO_SOAP_ENV_PREFIX));
- xmlNewNs(new_envelope,
- (xmlChar*)LASSO_SAML_ASSERTION_HREF, (xmlChar*)LASSO_SAML_ASSERTION_PREFIX);
- header = xmlNewTextChild(new_envelope, NULL, (xmlChar*)"Header", NULL);
+ /* Get the SOAP header */
+ lasso_extract_node_or_fail(header, envelope->Header, SOAP_HEADER,
+ LASSO_SOAP_ERROR_MISSING_HEADER);
+ goto_cleanup_if_fail_with_rc(header->Other && LASSO_IS_NODE(header->Other->data),
+ LASSO_SOAP_ERROR_MISSING_HEADER);
- /* PAOS request header block */
- soap_env_ns = xmlNewNs(new_envelope,
- (xmlChar*)LASSO_SOAP_ENV_HREF, (xmlChar*)LASSO_SOAP_ENV_PREFIX);
- paos_response = xmlNewNode(NULL, (xmlChar*)"Response");
- xmlSetNs(paos_response, xmlNewNs(paos_response,
- (xmlChar*)LASSO_PAOS_HREF, (xmlChar*)LASSO_PAOS_PREFIX));
- xmlSetNsProp(paos_response, soap_env_ns, (xmlChar*)"mustUnderstand", (xmlChar*)"1");
- xmlSetNsProp(paos_response, soap_env_ns, (xmlChar*)"actor",
- (xmlChar*)LASSO_SOAP_ENV_ACTOR);
- if (ecp->private_data->messageID) {
- xmlSetNsProp(paos_response, soap_env_ns, (xmlChar*)"refToMessageID",
- (xmlChar*)ecp->private_data->messageID);
- }
- xmlAddChild(header, paos_response);
+ /*
+ * Get the following header elements:
+ * * ecp:Response (required)
+ */
+ lasso_foreach(i, header->Other) {
+ if (!ecp_response && LASSO_IS_ECP_RESPONSE(i->data)) {
+ ecp_response = (LassoEcpResponse *)i->data;
+ }
- /* ECP relay state block */
- if (ecp->private_data->relay_state) {
- ecp_relay_state = xmlNewNode(NULL, (xmlChar*)"RelayState");
- xmlNodeSetContent(ecp_relay_state, (xmlChar*)ecp->private_data->relay_state);
- ecp_ns = xmlNewNs(ecp_relay_state, (xmlChar*)LASSO_ECP_HREF,
- (xmlChar*)LASSO_ECP_PREFIX);
- xmlSetNs(ecp_relay_state, ecp_ns);
- xmlSetNsProp(ecp_relay_state, soap_env_ns, (xmlChar*)"mustUnderstand",
- (xmlChar*)"1");
- xmlSetNsProp(ecp_relay_state, soap_env_ns, (xmlChar*)"actor",
- (xmlChar*)LASSO_SOAP_ENV_ACTOR);
- xmlAddChild(header, ecp_relay_state);
+ if (ecp_response) break;
}
- xmlAddChild(new_envelope, body);
- lasso_assign_new_string(LASSO_PROFILE(ecp)->msg_body,
- lasso_xmlnode_to_string(new_envelope, 0, 0))
- lasso_release_doc(doc);
- return 0;
+ goto_cleanup_if_fail_with_rc(ecp_response, LASSO_ECP_ERROR_MISSING_RESPONSE);
+
+ lasso_assign_string(ecp->assertion_consumer_url, ecp_response->AssertionConsumerServiceURL);
+
+ /*
+ * The ECP MUST confirm the ecp:Response
+ * AssertionConsumerServiceURL corresponds to the paos:Request
+ * responseConsumerURL. Since the responseConsumerServiceURL MAY
+ * be relative and the AssertionConsumerServiceURL is absolute
+ * some processing/normalization may be required.
+ *
+ * If the values do not match the ECP MUST generate a SOAP fault
+ * and MUST not return the SAML response.
+ */
+
+ if (lasso_strisnotequal(ecp->response_consumer_url, ecp_response->AssertionConsumerServiceURL)) {
+ goto_cleanup_with_rc(LASSO_ECP_ERROR_ASSERTION_CONSUMER_URL_MISMATCH);
+ }
+
+ /* Generate SOAP headers */
+ paos_response = LASSO_PAOS_RESPONSE(lasso_paos_response_new(ecp->message_id));
+ lasso_list_add_new_gobject(headers, paos_response);
+ if (ecp->relaystate) {
+ ecp_relaystate = LASSO_ECP_RELAYSTATE(lasso_ecp_relay_state_new(ecp->relaystate));
+ lasso_list_add_new_gobject(headers, ecp_relaystate);
+ }
+
+ /*
+ * Create a SOAP document and assign it to the LassoEcp->msg_body.
+ * See comment in lasso_ecp_process_authn_request_msg() where the
+ * profile->msg_body is assigned for an explanation of what is
+ * being done here.
+ */
+ {
+ xmlNodePtr xml;
+ LassoMiscTextNode *misc;
+
+ xml = lasso_node_get_original_xmlnode(LASSO_NODE(samlp2_response));
+
+ misc = lasso_misc_text_node_new_with_xml_node(xml);
+
+ lasso_assign_new_string(LASSO_PROFILE(ecp)->msg_body,
+ lasso_node_export_to_soap_with_headers(LASSO_NODE(misc),
+ headers));
+ lasso_release_gobject(misc);
+ }
+
+ /* Set the destination URL for the the PAOS response */
+ lasso_assign_string(LASSO_PROFILE(ecp)->msg_url, ecp->response_consumer_url);
+
+ cleanup:
+ if (rc) {
+ LassoSoapFault *fault = NULL;
+
+ fault = lasso_soap_fault_new_full(LASSO_SOAP_FAULT_CODE_CLIENT, lasso_strerror(rc));
+ lasso_assign_new_string(LASSO_PROFILE(ecp)->msg_body, lasso_node_export_to_soap(LASSO_NODE(fault)));
+ }
+
+ lasso_release_list_of_gobjects(headers);
+ lasso_release_gobject(envelope);
+
+ return rc;
}
GType
diff --git a/lasso/saml-2.0/ecp.h b/lasso/saml-2.0/ecp.h
index 19bcac55..18eb54a3 100644
--- a/lasso/saml-2.0/ecp.h
+++ b/lasso/saml-2.0/ecp.h
@@ -31,6 +31,7 @@ extern "C" {
#include "../xml/xml.h"
#include "../id-ff/profile.h"
+#include "../xml//saml-2.0/samlp2_idp_list.h"
#define LASSO_TYPE_ECP (lasso_ecp_get_type())
#define LASSO_ECP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), LASSO_TYPE_ECP, LassoEcp))
@@ -47,7 +48,16 @@ struct _LassoEcp {
LassoProfile parent;
/*< public >*/
- gchar *assertionConsumerURL;
+ gchar *assertion_consumer_url;
+ gchar *message_id;
+ gchar *response_consumer_url;
+ gchar *relaystate;
+ LassoSaml2NameID *issuer;
+ gchar *provider_name;
+ gboolean is_passive;
+ LassoSamlp2IDPList *sp_idp_list;
+ GList *known_sp_provided_idp_entries_supporting_ecp; /* of LassoSamlp2IDPEntry */
+ GList *known_idp_entity_ids_supporting_ecp; /* of strings */
/*< private >*/
LassoEcpPrivate *private_data;
@@ -69,6 +79,19 @@ LASSO_EXPORT lasso_error_t lasso_ecp_process_response_msg(LassoEcp *ecp,
LASSO_EXPORT void lasso_ecp_destroy(LassoEcp *ecp);
+LASSO_EXPORT gboolean lasso_ecp_is_provider_in_sp_idplist(LassoEcp *ecp, const gchar *entity_id);
+
+LASSO_EXPORT gboolean lasso_ecp_is_idp_entry_known_idp_supporting_ecp(LassoEcp *ecp, const LassoSamlp2IDPEntry *idp_entry);
+
+LASSO_EXPORT void lasso_ecp_set_known_sp_provided_idp_entries_supporting_ecp(LassoEcp *ecp);
+
+LASSO_EXPORT gboolean lasso_ecp_has_sp_idplist(LassoEcp *ecp);
+
+LASSO_EXPORT gchar *lasso_ecp_get_endpoint_url_by_entity_id(LassoEcp *ecp, const gchar *entity_id);
+
+LASSO_EXPORT int lasso_ecp_process_sp_idp_list(LassoEcp *ecp, const LassoSamlp2IDPList *sp_idp_list);
+
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/lasso/saml-2.0/ecpprivate.h b/lasso/saml-2.0/ecpprivate.h
index ea318a32..cce24e70 100644
--- a/lasso/saml-2.0/ecpprivate.h
+++ b/lasso/saml-2.0/ecpprivate.h
@@ -32,8 +32,7 @@ extern "C" {
struct _LassoEcpPrivate
{
- xmlChar *messageID;
- xmlChar *relay_state;
+ gboolean dispose_has_run;
};
#ifdef __cplusplus
diff --git a/lasso/xml/saml-2.0/samlp2_authn_request.c b/lasso/xml/saml-2.0/samlp2_authn_request.c
index 409b4fb1..f782df50 100644
--- a/lasso/xml/saml-2.0/samlp2_authn_request.c
+++ b/lasso/xml/saml-2.0/samlp2_authn_request.c
@@ -112,6 +112,7 @@ class_init(LassoSamlp2AuthnRequestClass *klass)
parent_class = g_type_class_peek_parent(klass);
nclass->node_data = g_new0(LassoNodeClassData, 1);
+ nclass->node_data->keep_xmlnode = TRUE;
lasso_node_class_set_nodename(nclass, "AuthnRequest");
lasso_node_class_set_ns(nclass, LASSO_SAML2_PROTOCOL_HREF, LASSO_SAML2_PROTOCOL_PREFIX);
lasso_node_class_add_snippets(nclass, schema_snippets);