A /mellon/probeDisco endpoint replaces the builtin:get-metadata IdP
dicovery URL scheme. It is configured using the MellonProbeDiscoveryTimeout and MellonProbeDiscoveryIdP directives. git-svn-id: https://modmellon.googlecode.com/svn/trunk/mod_mellon2@113 a716ebb1-153a-0410-b759-cfb97c6a1b53
This commit is contained in:
parent
0a47fe1972
commit
7cb7b968e3
3
NEWS
3
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
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
|
|
57
README
57
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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."
|
||||
),
|
||||
|
@ -704,6 +745,21 @@ const command_rec auth_mellon_commands[] = {
|
|||
OR_AUTHCFG,
|
||||
"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,
|
||||
|
@ -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;
|
||||
|
|
|
@ -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.",
|
||||
|
|
Loading…
Reference in New Issue