summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--README57
-rw-r--r--auth_mellon.h2
-rw-r--r--auth_mellon_config.c86
-rw-r--r--auth_mellon_handler.c220
5 files changed, 282 insertions, 86 deletions
diff --git a/NEWS b/NEWS
index 51d0cfa..b633ac1 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,9 @@ Version 0.3.1
* Allow MellonUser variable to be translated through MellonSetEnv
+* A /mellon/probeDisco endpoint replaces the builtin:get-metadata
+ IdP dicovery URL scheme
+
Version 0.3.0
---------------------------------------------------------------------------
diff --git a/README b/README
index 86da346..e77370c 100644
--- a/README
+++ b/README
@@ -321,8 +321,17 @@ MellonPostCount 100
# The IdP discovery must redirect the user to the return URL,
# with retueniDParam set to the selected IdP providerID.
#
- # Alternatively, a simple built-in IdP discovery can be used,
- # by specifying "builtin:get-metadata?timeout=1"
+ # The builtin:get-metadata discovery URL is not supported anymore
+ # starting with 0.3.1. See MellonProbeDiscoveryTimeout for
+ # a replacement.
+ #
+ # Default: None set.
+ MellonDiscoveryURL "http://www.example.net/idp-discovery"
+
+ # MellonProbeDiscoveryTimeout sets the timeout of the
+ # IdP probe discovery service, which is available on the
+ # probeDisco endoint.
+ #
# This will cause the SP to send HTTP GET requests on the
# configured IdP PorviderID URL. Theses URL should be used to
# publish metadata, though this is not mandatory. If the IdP
@@ -330,8 +339,16 @@ MellonPostCount 100
# If the PorviderID URL requires SSL, MellonIdPCAFile is used
# as a trusted CA bundle.
#
- # Default: None set.
- MellonDiscoveryURL "http://www.example.net/idp-discovery"
+ # Default: unset, which means the feature is disabled
+ # MellonProbeDiscoveryTimeout 1
+
+ # MellonProbeDiscoveryIdP can be used to restrict the
+ # list of IdP queried by the IdP probe discovery service.
+ #
+ # Default unset, which means that all configured IdP are
+ # queried.
+ # MellonProbeDiscoveryIdP http://idp1.example.com/saml/metadata
+ # MellonProbeDiscoveryIdP http://idp2.example.net/saml/metadata
# This option will make the SAML authentication assertion
# available in the MELLON_SAML_RESPONSE environement
@@ -476,6 +493,38 @@ could contain a link element like the following:
This will return the user to "https://www.example.org/logged_out.html"
after the logout operation has completed.
+===========================================================================
+ Probe IdP discovery
+===========================================================================
+
+mod_auth_mellon has an IdP probe discovery service that sends HTTP GET
+to IdP and picks the first that answers. This can be used as a poor
+man's failover setup that redirects to your organisation internal IdP.
+Here is a sample configuration:
+
+ MellonEndpointPath "/saml"
+ (...)
+ MellonDiscoveryUrl "/saml/probeDisco"
+ MellonProbeDiscoveryTimeout 1
+
+The SP will sends HTTP GET to each configured IdP providerId URL until
+it gets an HTTP 200 response within the 1 second timeout. It will then
+proceed with that IdP.
+
+If you are in a federation, then your IdP login page will need to provide
+an IdP selection feature aimed at users from other institutions (after
+such a choice, the user would be redirected to the SP's /saml/login
+endpoint, with ReturnTo and IdP set appropriately). In such a setup,
+you will want to configure external IdP in mod_auth_mellon, but not
+use them for IdP probe discovery. The MellonProbeDiscoveryIdP
+directive can be used to limit the usable IdP for probe discovery:
+
+ MellonEndpointPath "/saml"
+ (...)
+ MellonDiscoveryUrl "/saml/probeDisco"
+ MellonProbeDiscoveryTimeout 1
+ MellonProbeDiscoveryIdP "https://idp1.example.net/saml/metadata"
+ MellonProbeDiscoveryIdP "https://idp2.example.net/saml/metadata"
===========================================================================
Contributors
diff --git a/auth_mellon.h b/auth_mellon.h
index 835ccd1..247b6a6 100644
--- a/auth_mellon.h
+++ b/auth_mellon.h
@@ -174,6 +174,8 @@ typedef struct am_dir_cfg_rec {
/* IdP discovery service */
const char *discovery_url;
+ int probe_discovery_timeout;
+ apr_hash_t *probe_discovery_idp;
/* Mutex to prevent us from creating several lasso server objects. */
apr_thread_mutex_t *server_mutex;
diff --git a/auth_mellon_config.c b/auth_mellon_config.c
index 228240b..eba1fa4 100644
--- a/auth_mellon_config.c
+++ b/auth_mellon_config.c
@@ -76,6 +76,47 @@ static const apr_size_t post_size = 1024 * 1024 * 1024;
*/
static const int post_count = 100;
+/* This function handles configuration directives which set a
+ * multivalued string slot in the module configuration (the destination
+ * strucure is a hash).
+ *
+ * Parameters:
+ * cmd_parms *cmd The command structure for this configuration
+ * directive.
+ * void *struct_ptr Pointer to the current directory configuration.
+ * NULL if we are not in a directory configuration.
+ * This value isn't used by this function.
+ * const char *key The string argument following this configuration
+ * directive in the configuraion file.
+ * const char *value Optional value to be stored in the hash.
+ *
+ * Returns:
+ * NULL on success or an error string on failure.
+ */
+static const char *am_set_hash_string_slot(cmd_parms *cmd,
+ void *struct_ptr,
+ const char *key,
+ const char *value)
+{
+ server_rec *s = cmd->server;
+ apr_pool_t *pconf = s->process->pconf;
+ am_dir_cfg_rec *cfg = (am_dir_cfg_rec *)struct_ptr;
+ int offset;
+ apr_hash_t **hash;
+
+ /*
+ * If no value is given, we just store the key in the hash.
+ */
+ if (value == NULL || *value == '\0')
+ value = key;
+
+ offset = (int)(long)cmd->info;
+ hash = (apr_hash_t **)((char *)cfg + offset);
+ apr_hash_set(*hash, apr_pstrdup(pconf, key), APR_HASH_KEY_STRING, value);
+
+ return NULL;
+}
+
/* This function handles configuration directives which set a file
* slot in the module configuration. If lasso is recent enough, it
* attempts to read the file immediatly.
@@ -133,10 +174,10 @@ static const char *am_set_filestring_slot(cmd_parms *cmd,
* NULL on success or an error string on failure.
*
*/
-static const char *am_get_proovider_id(apr_pool_t *p,
- server_rec *s,
- const char *file,
- const char **provider)
+static const char *am_get_provider_id(apr_pool_t *p,
+ server_rec *s,
+ const char *file,
+ const char **provider)
{
char *data;
apr_xml_parser *xp;
@@ -195,7 +236,7 @@ static const char *am_get_proovider_id(apr_pool_t *p,
* Returns:
* NULL on success or an error string on failure.
*/
-static const char *ap_set_idp_string_slot(cmd_parms *cmd,
+static const char *am_set_idp_string_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
@@ -205,8 +246,8 @@ static const char *ap_set_idp_string_slot(cmd_parms *cmd,
const char *error;
const char *provider_id;
- if ((error = am_get_proovider_id(cmd->pool, s,
- arg, &provider_id)) != NULL)
+ if ((error = am_get_provider_id(cmd->pool, s,
+ arg, &provider_id)) != NULL)
return apr_psprintf(cmd->pool, "%s - %s", cmd->cmd->name, error);
apr_hash_set(cfg->idp_metadata_files,
@@ -649,8 +690,8 @@ const command_rec auth_mellon_commands[] = {
),
AP_INIT_TAKE1(
"MellonIdPMetadataFile",
- ap_set_idp_string_slot,
- NULL,
+ am_set_idp_string_slot,
+ NULL,
OR_AUTHCFG,
"Full path to xml metadata file for the IdP."
),
@@ -705,6 +746,21 @@ const command_rec auth_mellon_commands[] = {
"The URL of IdP discovery service. Default is unset."
),
AP_INIT_TAKE1(
+ "MellonProbeDiscoveryTimeout",
+ ap_set_int_slot,
+ (void *)APR_OFFSETOF(am_dir_cfg_rec, probe_discovery_timeout),
+ OR_AUTHCFG,
+ "The timeout of IdP probe discovery service. "
+ "Default is 1s"
+ ),
+ AP_INIT_TAKE12(
+ "MellonProbeDiscoveryIdP",
+ am_set_hash_string_slot,
+ (void *)APR_OFFSETOF(am_dir_cfg_rec, probe_discovery_idp),
+ OR_AUTHCFG,
+ "An IdP that can be used for IdP probe discovery."
+ ),
+ AP_INIT_TAKE1(
"MellonEndpointPath",
am_set_endpoint_path,
NULL,
@@ -760,6 +816,8 @@ void *auth_mellon_dir_config(apr_pool_t *p, char *d)
dir->idp_ca_file = NULL;
dir->login_path = default_login_path;
dir->discovery_url = NULL;
+ dir->probe_discovery_timeout = -1; /* -1 means no probe discovery */
+ dir->probe_discovery_idp = apr_hash_make(p);
dir->sp_org_name = apr_hash_make(p);
dir->sp_org_display_name = apr_hash_make(p);
@@ -903,6 +961,16 @@ void *auth_mellon_dir_merge(apr_pool_t *p, void *base, void *add)
add_cfg->discovery_url :
base_cfg->discovery_url);
+ new_cfg->probe_discovery_timeout =
+ (add_cfg->probe_discovery_timeout != -1 ?
+ add_cfg->probe_discovery_timeout :
+ base_cfg->probe_discovery_timeout);
+
+ new_cfg->probe_discovery_idp = apr_hash_copy(p,
+ (apr_hash_count(add_cfg->probe_discovery_idp) > 0) ?
+ add_cfg->probe_discovery_idp :
+ base_cfg->probe_discovery_idp);
+
apr_thread_mutex_create(&new_cfg->server_mutex,
APR_THREAD_MUTEX_DEFAULT, p);
new_cfg->server = NULL;
diff --git a/auth_mellon_handler.c b/auth_mellon_handler.c
index 02b996b..5590ca8 100644
--- a/auth_mellon_handler.c
+++ b/auth_mellon_handler.c
@@ -226,33 +226,6 @@ static const char *am_first_idp(request_rec *r)
return provider_id;
}
-/* This returns built-in IdP discovery timeout
- *
- * Parameters:
- * request_rec *r The request we received.
- *
- * Returns:
- * the timeout, -1 if not enabled.
- */
-static long am_builtin_discovery_timeout(request_rec *r)
-{
- am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
- const char *builtin = "builtin:get-metadata";
- const char *timeout = "?timeout=";
- const char *cp;
- const long default_timeout = 1L;
-
- if ((cfg->discovery_url == NULL) ||
- (strncmp(cfg->discovery_url, builtin, strlen(builtin)) != 0))
- return -1;
-
- cp = cfg->discovery_url + strlen(builtin);
- if (strncmp(cp, timeout, strlen(timeout)) != 0)
- return default_timeout;
-
- cp += strlen(timeout);
- return atoi(cp);
-}
/* This function selects an IdP and returns its provider_id
*
@@ -267,8 +240,6 @@ static const char *am_get_idp(request_rec *r)
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
const char *idp_provider_id;
const char *idp_metadata_file;
- apr_hash_index_t *index;
- long timeout;
/*
* If we have a single IdP, return that one.
@@ -308,47 +279,6 @@ static const char *am_get_idp(request_rec *r)
return idp_provider_id;
}
- /*
- * If built-in IdP discovery is not configured, return default.
- */
- timeout = am_builtin_discovery_timeout(r);
- if (timeout == -1)
- return am_first_idp(r);
-
- /*
- * Otherwise, proceed with built-in IdP discovery:
- * send probes for all configures IdP to check availability.
- * The first to answer is chosen. On error, use default.
- */
- for (index = apr_hash_first(r->pool, cfg->idp_metadata_files);
- index;
- index = apr_hash_next(index)) {
- void *buffer;
- apr_size_t len;
- apr_ssize_t slen;
- long status;
-
- apr_hash_this(index,
- (const void **)&idp_provider_id,
- &slen,
- (void *)&idp_metadata_file);
-
- status = 0;
- if (am_httpclient_get(r, idp_provider_id, &buffer, &len,
- timeout, &status) != OK)
- continue;
-
- if (status != HTTP_OK) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
- "Cannot probe %s: IdP returned HTTP %ld",
- idp_provider_id, status);
- continue;
- }
-
- /* We got some succes */
- return idp_provider_id;
- }
-
/*
* No IdP answered, use default
* Perhaps we should redirect to an error page instead.
@@ -2506,7 +2436,6 @@ static int am_auth_new_ticket(request_rec *r)
/* Check if IdP discovery is in use and no IdP was selected yet */
if ((cfg->discovery_url != NULL) &&
- (am_builtin_discovery_timeout(r) == -1) && /* no built-in discovery */
(am_extract_query_parameter(r->pool, r->args, "IdP") == NULL)) {
char *discovery_url;
char *return_url;
@@ -2536,8 +2465,7 @@ static int am_auth_new_ticket(request_rec *r)
/* If IdP discovery is in use and we have an IdP selected,
* set the relay_state
*/
- if ((cfg->discovery_url != NULL) &&
- (am_builtin_discovery_timeout(r) == -1)) { /* no built-in discovery */
+ if (cfg->discovery_url != NULL) {
char *return_url;
return_url = am_extract_query_parameter(r->pool, r->args, "ReturnTo");
@@ -2615,6 +2543,150 @@ static int am_handle_login(request_rec *r)
return am_send_authn_request(r, idp, return_to, is_passive);
}
+/* This function handles requests to the probe discovery handler
+ *
+ * Parameters:
+ * request_rec *r The request.
+ *
+ * Returns:
+ * OK on success, or an error if any of the steps fail.
+ */
+static int am_handle_probe_discovery(request_rec *r) {
+ am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
+ const char *idp = NULL;
+ int timeout;
+ apr_hash_index_t *index;
+ char *return_to;
+ char *idp_param;
+ char *redirect_url;
+ int ret;
+
+ /*
+ * If built-in IdP discovery is not configured, return error.
+ * For now we only have the get-metadata metadata method, so this
+ * information is not saved in configuration nor it is checked here.
+ */
+ timeout = cfg->probe_discovery_timeout;
+ if (timeout == -1) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "probe discovery handler invoked but not "
+ "configured. Plase set MellonProbeDiscoveryTimeout.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
+ * Check for mandatory arguments early to avoid sending
+ * probles for nothing.
+ */
+ return_to = am_extract_query_parameter(r->pool, r->args, "return");
+ if(return_to == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Missing required return parameter.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ ret = am_urldecode(return_to);
+ if (ret != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, ret, r,
+ "Could not urldecode return value.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ idp_param = am_extract_query_parameter(r->pool, r->args, "returnIDParam");
+ if(idp_param == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Missing required returnIDParam parameter.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ ret = am_urldecode(idp_param);
+ if (ret != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, ret, r,
+ "Could not urldecode returnIDParam value.");
+ return HTTP_BAD_REQUEST;
+ }
+
+ /*
+ * Proceed with built-in IdP discovery.
+ *
+ * Send probes for all configured IdP to check availability.
+ * The first to answer is chosen, but the list of usable
+ * IdP can be restricted in configuration.
+ */
+ for (index = apr_hash_first(r->pool, cfg->idp_metadata_files);
+ index;
+ index = apr_hash_next(index)) {
+ void *dontcare;
+ const char *ping_url;
+ apr_size_t len;
+ apr_ssize_t slen;
+ long status;
+
+ apr_hash_this(index, (const void **)&idp,
+ &slen, (void *)&dontcare);
+ ping_url = idp;
+
+ /*
+ * If a list of IdP was given for probe discovery,
+ * skip any IdP that does not match.
+ */
+ if (apr_hash_count(cfg->probe_discovery_idp) != 0) {
+ char *value = apr_hash_get(cfg->probe_discovery_idp,
+ idp, APR_HASH_KEY_STRING);
+
+ if (value == NULL) {
+ /* idp not in list, try the next one */
+ idp = NULL;
+ continue;
+ } else {
+ /* idp in list, use the value as the ping url */
+ ping_url = value;
+ }
+ }
+
+ status = 0;
+ if (am_httpclient_get(r, ping_url, &dontcare, &len,
+ timeout, &status) != OK)
+ continue;
+
+ if (status != HTTP_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Cannot probe %s: \"%s\" returned HTTP %ld",
+ idp, ping_url, status);
+ continue;
+ }
+
+ /* We got some succes */
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "probeDiscovery using %s", idp);
+ break;
+ }
+
+ /*
+ * On failure, try default
+ */
+ if (idp == NULL) {
+ idp = am_first_idp(r);
+ if (idp == NULL) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "probeDiscovery found no usable IdP.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ } else {
+ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "probeDiscovery "
+ "failed, trying default IdP %s", idp);
+ }
+ }
+
+ redirect_url = apr_psprintf(r->pool, "%s%s%s=%s", return_to,
+ strchr(return_to, '?') ? "&" : "?",
+ am_urlencode(r->pool, idp_param),
+ am_urlencode(r->pool, idp));
+
+ apr_table_setn(r->headers_out, "Location", redirect_url);
+
+ return HTTP_SEE_OTHER;
+}
+
/* This function takes a request for an endpoint and passes it on to the
* correct handler function.
@@ -2656,6 +2728,8 @@ static int am_endpoint_handler(request_rec *r)
return am_handle_logout(r);
} else if(!strcmp(endpoint, "login")) {
return am_handle_login(r);
+ } else if(!strcmp(endpoint, "probeDisco")) {
+ return am_handle_probe_discovery(r);
} else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Endpoint \"%s\" not handled by mod_auth_mellon.",