3258 lines
104 KiB
C
3258 lines
104 KiB
C
/*
|
|
*
|
|
* auth_mellon_handler.c: an authentication apache module
|
|
* Copyright © 2003-2007 UNINETT (http://www.uninett.no/)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
|
|
#include "auth_mellon.h"
|
|
|
|
|
|
#ifdef HAVE_lasso_server_new_from_buffers
|
|
# define SERVER_NEW lasso_server_new_from_buffers
|
|
#else /* HAVE_lasso_server_new_from_buffers */
|
|
# define SERVER_NEW lasso_server_new
|
|
#endif /* HAVE_lasso_server_new_from_buffers */
|
|
|
|
|
|
|
|
#ifdef HAVE_lasso_server_new_from_buffers
|
|
/* This function generates optional metadata for a given element
|
|
*
|
|
* Parameters:
|
|
* apr_pool_t *p Pool to allocate memory from
|
|
* apr_hash_t *t Hash of lang -> strings
|
|
* const char *e Name of the element
|
|
*
|
|
* Returns:
|
|
* the metadata, or NULL if an error occured
|
|
*/
|
|
static char *am_optional_metadata_element(apr_pool_t *p,
|
|
apr_hash_t *h,
|
|
const char *e)
|
|
{
|
|
apr_hash_index_t *index;
|
|
char *data = "";
|
|
|
|
for (index = apr_hash_first(p, h); index; index = apr_hash_next(index)) {
|
|
char *lang;
|
|
char *value;
|
|
apr_ssize_t slen;
|
|
char *xmllang = "";
|
|
|
|
apr_hash_this(index, (const void **)&lang, &slen, (void *)&value);
|
|
|
|
if (*lang != '\0')
|
|
xmllang = apr_psprintf(p, " xml:lang=\"%s\"", lang);
|
|
|
|
data = apr_psprintf(p, "%s<%s%s>%s</%s>",
|
|
data, e, xmllang, value, e);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/* This function generates optinal metadata
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* the metadata, or NULL if an error occured
|
|
*/
|
|
static char *am_optional_metadata(apr_pool_t *p, request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
int count = 0;
|
|
char *org_data = NULL;
|
|
char *org_name = NULL;
|
|
char *org_display_name = NULL;
|
|
char *org_url = NULL;
|
|
|
|
count += apr_hash_count(cfg->sp_org_name);
|
|
count += apr_hash_count(cfg->sp_org_display_name);
|
|
count += apr_hash_count(cfg->sp_org_url);
|
|
|
|
if (count == 0)
|
|
return "";
|
|
|
|
org_name = am_optional_metadata_element(p, cfg->sp_org_name,
|
|
"OrganizationName");
|
|
org_display_name = am_optional_metadata_element(p, cfg->sp_org_display_name,
|
|
"OrganizationDisplayName");
|
|
org_url = am_optional_metadata_element(p, cfg->sp_org_url,
|
|
"OrganizationURL");
|
|
org_data = apr_psprintf(p, "<Organization>%s%s%s</Organization>",
|
|
org_name, org_display_name, org_url);
|
|
|
|
return org_data;
|
|
}
|
|
|
|
|
|
/* This function generates metadata
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* the metadata, or NULL if an error occured
|
|
*/
|
|
static char *am_generate_metadata(apr_pool_t *p, request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
char *url = am_get_endpoint_url(r);
|
|
char *cert = "";
|
|
|
|
if (cfg->sp_cert_file) {
|
|
char *sp_cert_file;
|
|
char *cp;
|
|
char *bp;
|
|
const char *begin = "-----BEGIN CERTIFICATE-----";
|
|
const char *end = "-----END CERTIFICATE-----";
|
|
|
|
/*
|
|
* Try to remove leading and trailing garbage, as it can
|
|
* wreak havoc XML parser if it contains [<>&]
|
|
*/
|
|
sp_cert_file = apr_pstrdup(p, cfg->sp_cert_file);
|
|
|
|
cp = strstr(sp_cert_file, begin);
|
|
if (cp != NULL)
|
|
sp_cert_file = cp + strlen(begin);
|
|
|
|
cp = strstr(sp_cert_file, end);
|
|
if (cp != NULL)
|
|
*cp = '\0';
|
|
|
|
/*
|
|
* And remove any non printing char (CR, spaces...)
|
|
*/
|
|
bp = sp_cert_file;
|
|
for (cp = sp_cert_file; *cp; cp++) {
|
|
if (apr_isgraph(*cp))
|
|
*bp++ = *cp;
|
|
}
|
|
*bp = '\0';
|
|
|
|
cert = apr_psprintf(p,
|
|
"<KeyDescriptor use=\"signing\">"
|
|
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">"
|
|
"<ds:X509Data>"
|
|
"<ds:X509Certificate>%s</ds:X509Certificate>"
|
|
"</ds:X509Data>"
|
|
"</ds:KeyInfo>"
|
|
"</KeyDescriptor>"
|
|
"<KeyDescriptor use=\"encryption\">"
|
|
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">"
|
|
"<ds:X509Data>"
|
|
"<ds:X509Certificate>%s</ds:X509Certificate>"
|
|
"</ds:X509Data>"
|
|
"</ds:KeyInfo>"
|
|
"</KeyDescriptor>",
|
|
sp_cert_file,
|
|
sp_cert_file);
|
|
}
|
|
|
|
return apr_psprintf(p,
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
|
|
<EntityDescriptor\n\
|
|
entityID=\"%smetadata\"\n\
|
|
xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\">\n\
|
|
<SPSSODescriptor\n\
|
|
AuthnRequestsSigned=\"true\"\n\
|
|
WantAssertionsSigned=\"true\"\n\
|
|
protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n\
|
|
%s\
|
|
<SingleLogoutService\n\
|
|
Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:SOAP\"\n\
|
|
Location=\"%slogout\" />\n\
|
|
<SingleLogoutService\n\
|
|
Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n\
|
|
Location=\"%slogout\" />\n\
|
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n\
|
|
<AssertionConsumerService\n\
|
|
index=\"0\"\n\
|
|
isDefault=\"true\"\n\
|
|
Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n\
|
|
Location=\"%spostResponse\" />\n\
|
|
<AssertionConsumerService\n\
|
|
index=\"1\"\n\
|
|
Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\"\n\
|
|
Location=\"%sartifactResponse\" />\n\
|
|
</SPSSODescriptor>\n\
|
|
%s\n\
|
|
</EntityDescriptor>",
|
|
url, cert, url, url, url, url, am_optional_metadata(p, r));
|
|
}
|
|
#endif /* HAVE_lasso_server_new_from_buffers */
|
|
|
|
|
|
/*
|
|
* This function loads all IdP metadata in a lasso server
|
|
*
|
|
* Parameters:
|
|
* am_dir_cfg_rec *cfg The server configuration.
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* number of loaded providers
|
|
*/
|
|
static guint am_server_add_providers(am_dir_cfg_rec *cfg, request_rec *r)
|
|
{
|
|
const char *idp_public_key_file;
|
|
apr_size_t index;
|
|
|
|
if (cfg->idp_metadata->nelts == 1)
|
|
idp_public_key_file = cfg->idp_public_key_file;
|
|
else
|
|
idp_public_key_file = NULL;
|
|
|
|
for (index = 0; index < cfg->idp_metadata->nelts; index++) {
|
|
const am_metadata_t *idp_metadata;
|
|
int error;
|
|
#ifdef HAVE_lasso_server_load_metadata
|
|
GList *loaded_idp = NULL;
|
|
#endif /* HAVE_lasso_server_load_metadata */
|
|
|
|
idp_metadata = &( ((const am_metadata_t*)cfg->idp_metadata->elts) [index] );
|
|
|
|
#ifdef HAVE_lasso_server_load_metadata
|
|
error = lasso_server_load_metadata(cfg->server,
|
|
LASSO_PROVIDER_ROLE_IDP,
|
|
idp_metadata->file,
|
|
idp_metadata->chain,
|
|
cfg->idp_ignore,
|
|
&loaded_idp,
|
|
LASSO_SERVER_LOAD_METADATA_FLAG_DEFAULT);
|
|
if (error == 0) {
|
|
GList *idx;
|
|
|
|
for (idx = loaded_idp; idx != NULL; idx = idx->next) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"loaded IdP \"%s\" from \"%s\".",
|
|
(char *)idx->data, idp_metadata->file);
|
|
}
|
|
}
|
|
|
|
if (loaded_idp != NULL)
|
|
g_free(loaded_idp);
|
|
|
|
#else /* HAVE_lasso_server_load_metadata */
|
|
error = lasso_server_add_provider(cfg->server,
|
|
LASSO_PROVIDER_ROLE_IDP,
|
|
idp_metadata->file,
|
|
idp_public_key_file,
|
|
cfg->idp_ca_file);
|
|
#endif /* HAVE_lasso_server_load_metadata */
|
|
|
|
if (error != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error adding metadata \"%s\" to "
|
|
"lasso server objects: %s.",
|
|
idp_metadata->file, lasso_strerror(error));
|
|
}
|
|
}
|
|
|
|
return g_hash_table_size(cfg->server->providers);
|
|
}
|
|
|
|
|
|
static LassoServer *am_get_lasso_server(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
|
|
cfg = cfg->inherit_server_from;
|
|
|
|
apr_thread_mutex_lock(cfg->server_mutex);
|
|
if(cfg->server == NULL) {
|
|
if(cfg->sp_metadata_file == NULL) {
|
|
|
|
#ifdef HAVE_lasso_server_new_from_buffers
|
|
/*
|
|
* Try to generate missing metadata
|
|
*/
|
|
apr_pool_t *pool = r->server->process->pconf;
|
|
cfg->sp_metadata_file = am_generate_metadata(pool, r);
|
|
#else
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Missing MellonSPMetadataFile option.");
|
|
apr_thread_mutex_unlock(cfg->server_mutex);
|
|
return NULL;
|
|
#endif /* HAVE_lasso_server_new_from_buffers */
|
|
}
|
|
|
|
cfg->server = SERVER_NEW(cfg->sp_metadata_file,
|
|
cfg->sp_private_key_file,
|
|
NULL,
|
|
cfg->sp_cert_file);
|
|
if(cfg->server == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error initializing lasso server object. Please"
|
|
" verify the following configuration directives:"
|
|
" MellonSPMetadataFile and MellonSPPrivateKeyFile.");
|
|
|
|
apr_thread_mutex_unlock(cfg->server_mutex);
|
|
return NULL;
|
|
}
|
|
|
|
if (am_server_add_providers(cfg, r) == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error adding IdP to lasso server object. Please"
|
|
" verify the following configuration directives:"
|
|
" MellonIdPMetadataFile and"
|
|
" MellonIdPPublicKeyFile.");
|
|
|
|
lasso_server_destroy(cfg->server);
|
|
cfg->server = NULL;
|
|
|
|
apr_thread_mutex_unlock(cfg->server_mutex);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
apr_thread_mutex_unlock(cfg->server_mutex);
|
|
|
|
return cfg->server;
|
|
}
|
|
|
|
|
|
/* Redirect to discovery service.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
* const char *return_to The URL the user should be returned to after login.
|
|
*
|
|
* Returns:
|
|
* HTTP_SEE_OTHER on success, an error otherwise.
|
|
*/
|
|
static int am_start_disco(request_rec *r, const char *return_to)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
const char *endpoint = am_get_endpoint_url(r);
|
|
LassoServer *server;
|
|
const char *sp_entity_id;
|
|
const char *sep;
|
|
const char *login_url;
|
|
const char *discovery_url;
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL) {
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
sp_entity_id = LASSO_PROVIDER(server)->ProviderID;
|
|
|
|
login_url = apr_psprintf(r->pool, "%slogin?ReturnTo=%s",
|
|
endpoint,
|
|
am_urlencode(r->pool, return_to));
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"login_url = %s", login_url);
|
|
|
|
/* If discovery URL already has a ? we append a & */
|
|
sep = (strchr(cfg->discovery_url, '?')) ? "&" : "?";
|
|
|
|
discovery_url = apr_psprintf(r->pool, "%s%sentityID=%s&"
|
|
"return=%s&returnIDParam=IdP",
|
|
cfg->discovery_url, sep,
|
|
am_urlencode(r->pool, sp_entity_id),
|
|
am_urlencode(r->pool, login_url));
|
|
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"discovery_url = %s", discovery_url);
|
|
apr_table_setn(r->headers_out, "Location", discovery_url);
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
|
|
/* This function returns the first configured IdP
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* the providerID, or NULL if an error occured
|
|
*/
|
|
static const char *am_first_idp(request_rec *r)
|
|
{
|
|
LassoServer *server;
|
|
GList *idp_list;
|
|
const char *idp_providerid;
|
|
|
|
server = am_get_lasso_server(r);
|
|
if (server == NULL)
|
|
return NULL;
|
|
|
|
idp_list = g_hash_table_get_keys(server->providers);
|
|
if (idp_list == NULL)
|
|
return NULL;
|
|
|
|
idp_providerid = idp_list->data;
|
|
|
|
g_list_free(idp_list);
|
|
|
|
return idp_providerid;
|
|
}
|
|
|
|
|
|
/* This function selects an IdP and returns its provider_id
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* the provider_id, or NULL if an error occured
|
|
*/
|
|
static const char *am_get_idp(request_rec *r)
|
|
{
|
|
LassoServer *server;
|
|
const char *idp_provider_id;
|
|
|
|
server = am_get_lasso_server(r);
|
|
if (server == NULL)
|
|
return NULL;
|
|
|
|
/*
|
|
* If we have a single IdP, return that one.
|
|
*/
|
|
if (g_hash_table_size(server->providers) == 1)
|
|
return am_first_idp(r);
|
|
|
|
/*
|
|
* If IdP discovery handed us an IdP, try to use it.
|
|
*/
|
|
idp_provider_id = am_extract_query_parameter(r->pool, r->args, "IdP");
|
|
if (idp_provider_id != NULL) {
|
|
int rc;
|
|
|
|
rc = am_urldecode((char *)idp_provider_id);
|
|
if (rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Could not urldecode IdP discovery value.");
|
|
idp_provider_id = NULL;
|
|
} else {
|
|
if (g_hash_table_lookup(server->providers, idp_provider_id) == NULL)
|
|
idp_provider_id = NULL;
|
|
}
|
|
|
|
/*
|
|
* If we do not know about it, fall back to default.
|
|
*/
|
|
if (idp_provider_id == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"IdP discovery returned unknown or inexistant IdP");
|
|
idp_provider_id = am_first_idp(r);
|
|
}
|
|
|
|
return idp_provider_id;
|
|
}
|
|
|
|
/*
|
|
* No IdP answered, use default
|
|
* Perhaps we should redirect to an error page instead.
|
|
*/
|
|
return am_first_idp(r);
|
|
}
|
|
|
|
|
|
/* This function stores dumps of the LassoIdentity and LassoSession objects
|
|
* for the given LassoProfile object. The dumps are stored in the session
|
|
* belonging to the current request.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* am_cache_entry_t *session The session we are creating.
|
|
* LassoProfile *profile The profile object.
|
|
* char *saml_response The full SAML 2.0 response message.
|
|
*
|
|
* Returns:
|
|
* OK on success or HTTP_INTERNAL_SERVER_ERROR on failure.
|
|
*/
|
|
static int am_save_lasso_profile_state(request_rec *r,
|
|
am_cache_entry_t *session,
|
|
LassoProfile *profile,
|
|
char *saml_response)
|
|
{
|
|
LassoIdentity *lasso_identity;
|
|
LassoSession *lasso_session;
|
|
gchar *identity_dump;
|
|
gchar *session_dump;
|
|
int ret;
|
|
|
|
lasso_identity = lasso_profile_get_identity(profile);
|
|
if(lasso_identity == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"The current LassoProfile object doesn't contain a"
|
|
" LassoIdentity object.");
|
|
identity_dump = NULL;
|
|
} else {
|
|
identity_dump = lasso_identity_dump(lasso_identity);
|
|
if(identity_dump == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not create a identity dump from the"
|
|
" LassoIdentity object.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
}
|
|
|
|
lasso_session = lasso_profile_get_session(profile);
|
|
if(lasso_session == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"The current LassoProfile object doesn't contain a"
|
|
" LassoSession object.");
|
|
session_dump = NULL;
|
|
} else {
|
|
session_dump = lasso_session_dump(lasso_session);
|
|
if(session_dump == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not create a session dump from the"
|
|
" LassoSession object.");
|
|
if(identity_dump != NULL) {
|
|
g_free(identity_dump);
|
|
}
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
/* Save the profile state. */
|
|
ret = am_cache_set_lasso_state(session,
|
|
identity_dump,
|
|
session_dump,
|
|
saml_response);
|
|
|
|
if(identity_dump != NULL) {
|
|
g_free(identity_dump);
|
|
}
|
|
|
|
if(session_dump != NULL) {
|
|
g_free(session_dump);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Returns a SAML response
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* LassoProfile *profile The profile object.
|
|
*
|
|
* 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)
|
|
{
|
|
if (profile->msg_url && profile->msg_body) {
|
|
/* POST binding response */
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error building logout response message."
|
|
" POST binding is unsupported.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
} else if (profile->msg_url) {
|
|
/* HTTP-Redirect binding response */
|
|
apr_table_setn(r->headers_out, "Location",
|
|
apr_pstrdup(r->pool, profile->msg_url));
|
|
return HTTP_SEE_OTHER;
|
|
} else if (profile->msg_body) {
|
|
/* SOAP binding response */
|
|
ap_set_content_type(r, "text/xml");
|
|
ap_rputs(profile->msg_body, r);
|
|
return OK;
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error building logout response message."
|
|
" There is no content to return.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
/* This function restores dumps of a LassoIdentity object and a LassoSession
|
|
* object. The dumps are fetched from the session belonging to the current
|
|
* request and restored to the given LassoProfile object.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* LassoProfile *profile The profile object.
|
|
* am_cache_entry_t *am_session The session structure.
|
|
*
|
|
* Returns:
|
|
* OK on success or HTTP_INTERNAL_SERVER_ERROR on failure.
|
|
*/
|
|
static void am_restore_lasso_profile_state(request_rec *r,
|
|
LassoProfile *profile,
|
|
am_cache_entry_t *am_session)
|
|
{
|
|
const char *identity_dump;
|
|
const char *session_dump;
|
|
int rc;
|
|
|
|
|
|
if(am_session == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not get auth_mellon session while attempting"
|
|
" to restore the lasso profile state.");
|
|
return;
|
|
}
|
|
|
|
identity_dump = am_cache_get_lasso_identity(am_session);
|
|
if(identity_dump != NULL) {
|
|
rc = lasso_profile_set_identity_from_dump(profile, identity_dump);
|
|
if(rc < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not restore identity from dump."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
am_release_request_session(r, am_session);
|
|
}
|
|
}
|
|
|
|
session_dump = am_cache_get_lasso_session(am_session);
|
|
if(session_dump != NULL) {
|
|
rc = lasso_profile_set_session_from_dump(profile, session_dump);
|
|
if(rc < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not restore session from dump."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
am_release_request_session(r, am_session);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This function handles an IdP initiated logout request.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The logout request.
|
|
* LassoLogout *logout A LassoLogout object initiated with
|
|
* the current session.
|
|
*
|
|
* 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)
|
|
{
|
|
gint res = 0, rc = HTTP_OK;
|
|
am_cache_entry_t *session;
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
|
|
/* Process the logout message. Ignore missing signature. */
|
|
res = lasso_logout_process_request_msg(logout, msg);
|
|
#ifdef HAVE_lasso_profile_set_signature_verify_hint
|
|
if(res != 0 && res != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
|
|
if (apr_hash_get(cfg->do_not_verify_logout_signature,
|
|
logout->parent.remote_providerID,
|
|
APR_HASH_KEY_STRING)) {
|
|
lasso_profile_set_signature_verify_hint(&logout->parent,
|
|
LASSO_PROFILE_SIGNATURE_VERIFY_HINT_IGNORE);
|
|
res = lasso_logout_process_request_msg(logout, msg);
|
|
}
|
|
}
|
|
#endif
|
|
if(res != 0 && res != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error processing logout request message."
|
|
" Lasso error: [%i] %s", res, lasso_strerror(res));
|
|
|
|
rc = HTTP_BAD_REQUEST;
|
|
goto exit;
|
|
}
|
|
|
|
/* Search session using NameID */
|
|
if (! LASSO_IS_SAML2_NAME_ID(logout->parent.nameIdentifier)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error processing logout request message."
|
|
" No NameID found");
|
|
rc = HTTP_BAD_REQUEST;
|
|
goto exit;
|
|
}
|
|
session = am_get_request_session_by_nameid(r,
|
|
((LassoSaml2NameID*)logout->parent.nameIdentifier)->content);
|
|
if (session == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error processing logout request message."
|
|
" No session found for NameID %s",
|
|
((LassoSaml2NameID*)logout->parent.nameIdentifier)->content);
|
|
|
|
}
|
|
if (session == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error processing logout request message."
|
|
" No session found.");
|
|
|
|
} else {
|
|
am_restore_lasso_profile_state(r, &logout->parent, session);
|
|
}
|
|
|
|
/* Validate the logout message. Ignore missing signature. */
|
|
res = lasso_logout_validate_request(logout);
|
|
if(res != 0 &&
|
|
res != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND &&
|
|
res != LASSO_PROFILE_ERROR_SESSION_NOT_FOUND) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"Error validating logout request."
|
|
" Lasso error: [%i] %s", res, lasso_strerror(res));
|
|
rc = HTTP_INTERNAL_SERVER_ERROR;
|
|
goto exit;
|
|
}
|
|
/* We continue with the logout despite those errors. They could be
|
|
* caused by the IdP believing that we are logged in when we are not.
|
|
*/
|
|
|
|
/* Delete the session. */
|
|
if (session != NULL && res != LASSO_PROFILE_ERROR_SESSION_NOT_FOUND)
|
|
am_delete_request_session(r, session);
|
|
|
|
|
|
/* Create response message. */
|
|
res = lasso_logout_build_response_msg(logout);
|
|
if(res != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error building logout response message."
|
|
" Lasso error: [%i] %s", res, lasso_strerror(res));
|
|
|
|
rc = HTTP_INTERNAL_SERVER_ERROR;
|
|
goto exit;
|
|
}
|
|
rc = am_return_logout_response(r, &logout->parent);
|
|
|
|
exit:
|
|
lasso_logout_destroy(logout);
|
|
return rc;
|
|
}
|
|
|
|
|
|
/* This function handles a logout response message from the IdP. We get
|
|
* this message after we have sent a logout request to the IdP.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The logout response request.
|
|
* LassoLogout *logout A LassoLogout object initiated with
|
|
* the current session.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error if any of the steps fail.
|
|
*/
|
|
static int am_handle_logout_response(request_rec *r, LassoLogout *logout)
|
|
{
|
|
gint res;
|
|
int rc;
|
|
am_cache_entry_t *session;
|
|
char *return_to;
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
|
|
res = lasso_logout_process_response_msg(logout, r->args);
|
|
#ifdef HAVE_lasso_profile_set_signature_verify_hint
|
|
if(res != 0 && res != LASSO_DS_ERROR_SIGNATURE_NOT_FOUND) {
|
|
if (apr_hash_get(cfg->do_not_verify_logout_signature,
|
|
logout->parent.remote_providerID,
|
|
APR_HASH_KEY_STRING)) {
|
|
lasso_profile_set_signature_verify_hint(&logout->parent,
|
|
LASSO_PROFILE_SIGNATURE_VERIFY_HINT_IGNORE);
|
|
res = lasso_logout_process_response_msg(logout, r->args);
|
|
}
|
|
}
|
|
#endif
|
|
if(res != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unable to process logout response."
|
|
" Lasso error: [%i] %s", res, lasso_strerror(res));
|
|
|
|
lasso_logout_destroy(logout);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
lasso_logout_destroy(logout);
|
|
|
|
/* Delete the session. */
|
|
session = am_get_request_session(r);
|
|
if(session != NULL) {
|
|
am_delete_request_session(r, session);
|
|
}
|
|
|
|
return_to = am_extract_query_parameter(r->pool, r->args, "RelayState");
|
|
if(return_to == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No RelayState parameter to logout response handler."
|
|
" It is possible that your IdP doesn't support the"
|
|
" RelayState parameter.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
rc = am_urldecode(return_to);
|
|
if(rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Could not urldecode RelayState value in logout"
|
|
" response.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Check for bad characters in RelayState. */
|
|
rc = am_check_url(r, return_to);
|
|
if (rc != OK) {
|
|
return rc;
|
|
}
|
|
|
|
apr_table_setn(r->headers_out, "Location", return_to);
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
|
|
/* This function initiates a logout request and sends it to the IdP.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The logout response request.
|
|
* LassoLogout *logout A LassoLogout object initiated with
|
|
* the current session.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error if any of the steps fail.
|
|
*/
|
|
static int am_init_logout_request(request_rec *r, LassoLogout *logout)
|
|
{
|
|
char *return_to;
|
|
int rc;
|
|
am_cache_entry_t *mellon_session;
|
|
gint res;
|
|
char *redirect_to;
|
|
LassoProfile *profile;
|
|
LassoSession *session;
|
|
GList *assertion_list;
|
|
LassoNode *assertion_n;
|
|
LassoSaml2Assertion *assertion;
|
|
LassoSaml2AuthnStatement *authnStatement;
|
|
LassoSamlp2LogoutRequest *request;
|
|
|
|
return_to = am_extract_query_parameter(r->pool, r->args, "ReturnTo");
|
|
rc = am_urldecode(return_to);
|
|
if (rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Could not urldecode ReturnTo value.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Disable the the local session (in case the IdP doesn't respond). */
|
|
mellon_session = am_get_request_session(r);
|
|
if(mellon_session != NULL) {
|
|
am_restore_lasso_profile_state(r, &logout->parent, mellon_session);
|
|
mellon_session->logged_in = 0;
|
|
am_release_request_session(r, mellon_session);
|
|
}
|
|
|
|
/* Create the logout request message. */
|
|
res = lasso_logout_init_request(logout, NULL, LASSO_HTTP_METHOD_REDIRECT);
|
|
/* Early non failing return. */
|
|
if (res != 0) {
|
|
if(res == LASSO_PROFILE_ERROR_SESSION_NOT_FOUND) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"User attempted to initiate logout without being"
|
|
" loggged in.");
|
|
} else if (res == LASSO_LOGOUT_ERROR_UNSUPPORTED_PROFILE || res == LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Current identity provider "
|
|
"does not support single logout. Destroying local session only.");
|
|
|
|
} else if(res != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unable to create logout request."
|
|
" Lasso error: [%i] %s", res, lasso_strerror(res));
|
|
|
|
lasso_logout_destroy(logout);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
lasso_logout_destroy(logout);
|
|
/* Check for bad characters in ReturnTo. */
|
|
rc = am_check_url(r, return_to);
|
|
if (rc != OK) {
|
|
return rc;
|
|
}
|
|
/* Redirect to the page the user should be sent to after logout. */
|
|
apr_table_setn(r->headers_out, "Location", return_to);
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
profile = LASSO_PROFILE(logout);
|
|
|
|
/* We need to set the SessionIndex in the LogoutRequest to the SessionIndex
|
|
* we received during the login operation. This is not needed since release
|
|
* 2.3.0.
|
|
*/
|
|
if (lasso_check_version(2, 3, 0, LASSO_CHECK_VERSION_NUMERIC) == 0) {
|
|
session = lasso_profile_get_session(profile);
|
|
assertion_list = lasso_session_get_assertions(
|
|
session, profile->remote_providerID);
|
|
if(! assertion_list ||
|
|
LASSO_IS_SAML2_ASSERTION(assertion_list->data) == FALSE) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No assertions found for the current session.");
|
|
lasso_logout_destroy(logout);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
/* We currently only look at the first assertion in the list
|
|
* lasso_session_get_assertions returns.
|
|
*/
|
|
assertion_n = assertion_list->data;
|
|
|
|
assertion = LASSO_SAML2_ASSERTION(assertion_n);
|
|
|
|
/* We assume that the first authnStatement contains the data we want. */
|
|
authnStatement = LASSO_SAML2_AUTHN_STATEMENT(assertion->AuthnStatement->data);
|
|
|
|
if(!authnStatement) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No AuthnStatement found in the current assertion.");
|
|
lasso_logout_destroy(logout);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
if(authnStatement->SessionIndex) {
|
|
request = LASSO_SAMLP2_LOGOUT_REQUEST(profile->request);
|
|
request->SessionIndex = g_strdup(authnStatement->SessionIndex);
|
|
}
|
|
}
|
|
|
|
|
|
/* Set the RelayState parameter to the return url (if we have one). */
|
|
if(return_to) {
|
|
profile->msg_relayState = g_strdup(return_to);
|
|
}
|
|
|
|
/* Serialize the request message into a url which we can redirect to. */
|
|
res = lasso_logout_build_request_msg(logout);
|
|
if(res != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unable to serialize lasso logout message."
|
|
" Lasso error: [%i] %s", res, lasso_strerror(res));
|
|
|
|
lasso_logout_destroy(logout);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Set the redirect url. */
|
|
redirect_to = apr_pstrdup(r->pool, LASSO_PROFILE(logout)->msg_url);
|
|
|
|
/* Check if the lasso library added the RelayState. If lasso didn't add
|
|
* a RelayState parameter, then we add one ourself. This should hopefully
|
|
* be removed in the future.
|
|
*/
|
|
if(return_to != NULL
|
|
&& strstr(redirect_to, "&RelayState=") == NULL
|
|
&& strstr(redirect_to, "?RelayState=") == NULL) {
|
|
/* The url didn't contain the relaystate parameter. */
|
|
redirect_to = apr_pstrcat(
|
|
r->pool, redirect_to, "&RelayState=",
|
|
am_urlencode(r->pool, return_to),
|
|
NULL
|
|
);
|
|
}
|
|
|
|
apr_table_setn(r->headers_out, "Location", redirect_to);
|
|
|
|
lasso_logout_destroy(logout);
|
|
|
|
/* Redirect (without including POST data if this was a POST request. */
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
|
|
/* This function handles requests to the logout handler.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error if any of the steps fail.
|
|
*/
|
|
static int am_handle_logout(request_rec *r)
|
|
{
|
|
LassoServer *server;
|
|
LassoLogout *logout;
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL) {
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
logout = lasso_logout_new(server);
|
|
if(logout == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error creating lasso logout object.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Check which type of request to the logout handler this is.
|
|
* We have three types:
|
|
* - logout requests: The IdP sends a logout request to this service.
|
|
* it can be either through HTTP-Redirect or SOAP.
|
|
* - logout responses: We have sent a logout request to the IdP, and
|
|
* are receiving a response.
|
|
* - We want to initiate a logout request.
|
|
*/
|
|
|
|
/* First check for IdP-initiated SOAP logout request */
|
|
if ((r->args == NULL) && (r->method_number == M_POST)) {
|
|
int rc;
|
|
char *post_data;
|
|
|
|
rc = am_read_post_data(r, &post_data, NULL);
|
|
if (rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Error reading POST data.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
return am_handle_logout_request(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);
|
|
|
|
} else if(am_extract_query_parameter(r->pool, r->args,
|
|
"SAMLResponse") != NULL) {
|
|
/* SAMLResponse - logout response from the IdP. */
|
|
return am_handle_logout_response(r, logout);
|
|
|
|
} else if(am_extract_query_parameter(r->pool, r->args,
|
|
"ReturnTo") != NULL) {
|
|
/* RedirectTo - SP initiated logout. */
|
|
return am_init_logout_request(r, logout);
|
|
|
|
} else {
|
|
/* Unknown request to the logout handler. */
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No known parameters passed to the logout"
|
|
" handler. Query string was \"%s\". To initiate"
|
|
" a logout, you need to pass a \"ReturnTo\""
|
|
" parameter with a url to the web page the user should"
|
|
" be redirected to after a successful logout.",
|
|
r->args);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
|
|
/* This function parses a timestamp for a SAML 2.0 condition.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request. Used for logging of errors.
|
|
* const char *timestamp The timestamp we should parse. Must be on
|
|
* the following format: "YYYY-MM-DDThh:mm:ssZ"
|
|
*
|
|
* Returns:
|
|
* An apr_time_t value with the timestamp, or 0 on error.
|
|
*/
|
|
static apr_time_t am_parse_timestamp(request_rec *r, const char *timestamp)
|
|
{
|
|
size_t len;
|
|
int i;
|
|
char c;
|
|
const char *expected;
|
|
apr_time_exp_t time_exp;
|
|
apr_time_t res;
|
|
apr_status_t rc;
|
|
|
|
len = strlen(timestamp);
|
|
|
|
/* Verify length of timestamp. */
|
|
if(len < 20){
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"Invalid length of timestamp: \"%s\".", timestamp);
|
|
}
|
|
|
|
/* Verify components of timestamp. */
|
|
for(i = 0; i < len - 1; i++) {
|
|
c = timestamp[i];
|
|
|
|
expected = NULL;
|
|
|
|
switch(i) {
|
|
|
|
case 4:
|
|
case 7:
|
|
/* Matches " - - " */
|
|
if(c != '-') {
|
|
expected = "'-'";
|
|
}
|
|
break;
|
|
|
|
case 10:
|
|
/* Matches " T " */
|
|
if(c != 'T') {
|
|
expected = "'T'";
|
|
}
|
|
break;
|
|
|
|
case 13:
|
|
case 16:
|
|
/* Matches " : : " */
|
|
if(c != ':') {
|
|
expected = "':'";
|
|
}
|
|
break;
|
|
|
|
case 19:
|
|
/* Matches " ." */
|
|
if (c != '.') {
|
|
expected = "'.'";
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Matches "YYYY MM DD hh mm ss uuuuuu" */
|
|
if(c < '0' || c > '9') {
|
|
expected = "a digit";
|
|
}
|
|
break;
|
|
}
|
|
|
|
if(expected != NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid character in timestamp at position %i."
|
|
" Expected %s, got '%c'. Full timestamp: \"%s\"",
|
|
i, expected, c, timestamp);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (timestamp[len - 1] != 'Z') {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Timestamp wasn't in UTC (did not end with 'Z')."
|
|
" Full timestamp: \"%s\"",
|
|
timestamp);
|
|
return 0;
|
|
}
|
|
|
|
|
|
time_exp.tm_usec = 0;
|
|
if (len > 20) {
|
|
/* Subsecond precision. */
|
|
if (len > 27) {
|
|
/* Timestamp has more than microsecond precision. Just clip it to
|
|
* microseconds.
|
|
*/
|
|
len = 27;
|
|
}
|
|
len -= 1; /* Drop the 'Z' off the end. */
|
|
for (i = 20; i < len; i++) {
|
|
time_exp.tm_usec = time_exp.tm_usec * 10 + timestamp[i] - '0';
|
|
}
|
|
for (i = len; i < 26; i++) {
|
|
time_exp.tm_usec *= 10;
|
|
}
|
|
}
|
|
|
|
time_exp.tm_sec = (timestamp[17] - '0') * 10 + (timestamp[18] - '0');
|
|
time_exp.tm_min = (timestamp[14] - '0') * 10 + (timestamp[15] - '0');
|
|
time_exp.tm_hour = (timestamp[11] - '0') * 10 + (timestamp[12] - '0');
|
|
time_exp.tm_mday = (timestamp[8] - '0') * 10 + (timestamp[9] - '0');
|
|
time_exp.tm_mon = (timestamp[5] - '0') * 10 + (timestamp[6] - '0') - 1;
|
|
time_exp.tm_year = (timestamp[0] - '0') * 1000 +
|
|
(timestamp[1] - '0') * 100 + (timestamp[2] - '0') * 10 +
|
|
(timestamp[3] - '0') - 1900;
|
|
|
|
time_exp.tm_wday = 0; /* Unknown. */
|
|
time_exp.tm_yday = 0; /* Unknown. */
|
|
|
|
time_exp.tm_isdst = 0; /* UTC, no daylight savings time. */
|
|
time_exp.tm_gmtoff = 0; /* UTC, no offset from UTC. */
|
|
|
|
rc = apr_time_exp_gmt_get(&res, &time_exp);
|
|
if(rc != APR_SUCCESS) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Error converting timestamp \"%s\".",
|
|
timestamp);
|
|
return 0;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/* Validate the subject on an Assertion.
|
|
*
|
|
* request_rec *r The current request. Used to log
|
|
* errors.
|
|
* LassoSaml2Assertion *assertion The assertion we will validate.
|
|
* const char *url The current URL.
|
|
*
|
|
* Returns:
|
|
* OK on success, HTTP_BAD_REQUEST on failure.
|
|
*/
|
|
static int am_validate_subject(request_rec *r, LassoSaml2Assertion *assertion,
|
|
const char *url)
|
|
{
|
|
apr_time_t now;
|
|
apr_time_t t;
|
|
LassoSaml2SubjectConfirmation *sc;
|
|
LassoSaml2SubjectConfirmationData *scd;
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
|
|
if (assertion->Subject == NULL) {
|
|
/* No Subject to validate. */
|
|
return OK;
|
|
} else if (!LASSO_IS_SAML2_SUBJECT(assertion->Subject)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of Subject node.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
if (assertion->Subject->SubjectConfirmation == NULL) {
|
|
/* No SubjectConfirmation. */
|
|
return OK;
|
|
} else if (!LASSO_IS_SAML2_SUBJECT_CONFIRMATION(assertion->Subject->SubjectConfirmation)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of SubjectConfirmation node.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
sc = assertion->Subject->SubjectConfirmation;
|
|
if (sc->Method == NULL ||
|
|
strcmp(sc->Method, "urn:oasis:names:tc:SAML:2.0:cm:bearer")) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid Method in SubjectConfirmation.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
scd = sc->SubjectConfirmationData;
|
|
if (scd == NULL) {
|
|
/* Nothing to verify. */
|
|
return OK;
|
|
} else if (!LASSO_IS_SAML2_SUBJECT_CONFIRMATION_DATA(scd)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of SubjectConfirmationData node.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
now = apr_time_now();
|
|
|
|
if (scd->NotBefore) {
|
|
t = am_parse_timestamp(r, scd->NotBefore);
|
|
if (t == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid timestamp in NotBefore in SubjectConfirmationData.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
if (t - 60000000 > now) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"NotBefore in SubjectConfirmationData was in the future.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
if (scd->NotOnOrAfter) {
|
|
t = am_parse_timestamp(r, scd->NotOnOrAfter);
|
|
if (t == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid timestamp in NotOnOrAfter in SubjectConfirmationData.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
if (now >= t + 60000000) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"NotOnOrAfter in SubjectConfirmationData was in the past.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
if (scd->Recipient) {
|
|
if (strcmp(scd->Recipient, url)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong Recipient in SubjectConfirmationData. Current URL is: %s, Recipient is %s",
|
|
url, scd->Recipient);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
if (scd->Address && CFG_VALUE(cfg, subject_confirmation_data_address_check)) {
|
|
if (strcasecmp(scd->Address, r->connection->remote_ip)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong Address in SubjectConfirmationData."
|
|
"Current address is \"%s\", but should have been \"%s\".",
|
|
r->connection->remote_ip, scd->Address);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/* Validate the conditions on an Assertion.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request. Used to log
|
|
* errors.
|
|
* LassoSaml2Assertion *assertion The assertion we will validate.
|
|
* const char *providerID The providerID of the SP.
|
|
*
|
|
* Returns:
|
|
* OK on success, HTTP_BAD_REQUEST on failure.
|
|
*/
|
|
static int am_validate_conditions(request_rec *r,
|
|
LassoSaml2Assertion *assertion,
|
|
const char *providerID)
|
|
{
|
|
LassoSaml2Conditions *conditions;
|
|
apr_time_t now;
|
|
apr_time_t t;
|
|
GList *i;
|
|
LassoSaml2AudienceRestriction *ar;
|
|
|
|
conditions = assertion->Conditions;
|
|
if (!LASSO_IS_SAML2_CONDITIONS(conditions)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of Conditions node.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
if (conditions->Condition != NULL) {
|
|
/* This is a list of LassoSaml2ConditionAbstract - if it
|
|
* isn't empty, we have an unsupported condition.
|
|
*/
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unsupported condition in Assertion.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
|
|
now = apr_time_now();
|
|
|
|
if (conditions->NotBefore) {
|
|
t = am_parse_timestamp(r, conditions->NotBefore);
|
|
if (t == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid timestamp in NotBefore in Condition.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
if (t - 60000000 > now) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"NotBefore in Condition was in the future.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
if (conditions->NotOnOrAfter) {
|
|
t = am_parse_timestamp(r, conditions->NotOnOrAfter);
|
|
if (t == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid timestamp in NotOnOrAfter in Condition.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
if (now >= t + 60000000) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"NotOnOrAfter in Condition was in the past.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
for (i = g_list_first(conditions->AudienceRestriction); i != NULL;
|
|
i = g_list_next(i)) {
|
|
ar = i->data;
|
|
if (!LASSO_IS_SAML2_AUDIENCE_RESTRICTION(ar)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of AudienceRestriction node.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
if (ar->Audience == NULL || strcmp(ar->Audience, providerID)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid Audience in Conditions. Should be: %s",
|
|
providerID);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
|
|
/* This function sets the session expire timestamp based on NotOnOrAfter
|
|
* attribute of a condition element.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request. Used to log
|
|
* errors.
|
|
* am_cache_entry_t *session The current session.
|
|
* LassoSaml2Assertion *assertion The assertion which we will extract
|
|
* the conditions from.
|
|
*
|
|
* Returns:
|
|
* Nothing.
|
|
*/
|
|
static void am_handle_session_expire(request_rec *r, am_cache_entry_t *session,
|
|
LassoSaml2Assertion *assertion)
|
|
{
|
|
GList *authn_itr;
|
|
LassoSaml2AuthnStatement *authn;
|
|
const char *not_on_or_after;
|
|
apr_time_t t;
|
|
|
|
for(authn_itr = g_list_first(assertion->AuthnStatement); authn_itr != NULL;
|
|
authn_itr = g_list_next(authn_itr)) {
|
|
|
|
authn = authn_itr->data;
|
|
if (!LASSO_IS_SAML2_AUTHN_STATEMENT(authn)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of AuthnStatement node.");
|
|
continue;
|
|
}
|
|
|
|
/* Find timestamp. */
|
|
not_on_or_after = authn->SessionNotOnOrAfter;
|
|
if(not_on_or_after == NULL) {
|
|
continue;
|
|
}
|
|
|
|
|
|
/* Parse timestamp. */
|
|
t = am_parse_timestamp(r, not_on_or_after);
|
|
if(t == 0) {
|
|
continue;
|
|
}
|
|
|
|
/* Updates the expires timestamp if this one is earlier than the
|
|
* previous timestamp.
|
|
*/
|
|
am_cache_update_expires(session, t);
|
|
}
|
|
}
|
|
|
|
|
|
/* This function is for decoding and storing attributes with the feide
|
|
* encoding. It takes in an attribute name and a value. The value is split
|
|
* into multiple values. We base64 decode these values, and store them in the
|
|
* session data.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* am_cache_entry_t *session The current session.
|
|
* const char *name Name of the attribute.
|
|
* const char *value The value(s) of the attribute.
|
|
*
|
|
* Returns:
|
|
* OK on success or an error from am_cache_env_append(...) if it was unable
|
|
* to store the attribute.
|
|
*/
|
|
static int am_store_attribute_feide(request_rec *r, am_cache_entry_t *session,
|
|
const char *name, const char *value)
|
|
{
|
|
char *edit_value;
|
|
char *start;
|
|
char *next;
|
|
int len;
|
|
int ret;
|
|
|
|
/* We need to be able to change the value. */
|
|
edit_value = apr_pstrdup(r->pool, value);
|
|
|
|
for(start = edit_value; start != NULL; start = next) {
|
|
/* The values are separated by '_'. */
|
|
next = strchr(start, '_');
|
|
|
|
if(next != NULL) {
|
|
/* Insert null-terminator after current value. */
|
|
*next = '\0';
|
|
|
|
/* The next value begins at next+1. */
|
|
next++;
|
|
}
|
|
|
|
/* Now start points to the current value, which we have
|
|
* null-terminated. next points to the next value, or NULL if
|
|
* this is the last value.
|
|
*/
|
|
|
|
/* base64-decode current value.
|
|
* From looking at the source of apr_base64_decode_binary, it
|
|
* appears to be safe to use in-place.
|
|
*/
|
|
len = apr_base64_decode_binary((unsigned char *)start, start);
|
|
|
|
/* Add null-terminator at end of string. */
|
|
start[len] = '\0';
|
|
|
|
|
|
/* Store current name-value-pair. */
|
|
ret = am_cache_env_append(session, name, start);
|
|
if(ret != OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
/* This function is for storing attributes without any encoding. We just store
|
|
* the attribute as it is.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* am_cache_entry_t *session The current session.
|
|
* const char *name The name of the attribute.
|
|
* const char *value The value of the attribute.
|
|
*
|
|
* Returns:
|
|
* OK on success or an error from am_cache_env_append(...) if it failed.
|
|
*/
|
|
static int am_store_attribute_none(request_rec *r, am_cache_entry_t *session,
|
|
const char *name, const char *value)
|
|
{
|
|
/* Store current name-value-pair. */
|
|
return am_cache_env_append(session, name, value);
|
|
}
|
|
|
|
|
|
/* This function passes a name-value pair to the decoder selected by the
|
|
* MellonDecoder configuration option. The decoder will decode the value
|
|
* and store it in the session data.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* am_cache_entry_t *session The current session.
|
|
* const char *name The name of the attribute.
|
|
* const char *value The value of the attribute.
|
|
*
|
|
* Returns:
|
|
* OK on success or an error from the attribute decoder if it failed.
|
|
*/
|
|
static int am_store_attribute(request_rec *r, am_cache_entry_t *session,
|
|
const char *name, const char *value)
|
|
{
|
|
am_dir_cfg_rec *dir_cfg;
|
|
|
|
dir_cfg = am_get_dir_cfg(r);
|
|
|
|
switch(dir_cfg->decoder) {
|
|
case am_decoder_none:
|
|
return am_store_attribute_none(r, session, name, value);
|
|
|
|
case am_decoder_feide:
|
|
return am_store_attribute_feide(r, session, name, value);
|
|
|
|
default:
|
|
return am_store_attribute_none(r, session, name, value);
|
|
}
|
|
}
|
|
|
|
|
|
/* Add all the attributes from an assertion to the session data for the
|
|
* current user.
|
|
*
|
|
* Parameters:
|
|
* am_cache_entry_t *s The current session.
|
|
* request_rec *r The current request.
|
|
* const char *name_id The name identifier we received from
|
|
* the IdP.
|
|
* LassoSaml2Assertion *assertion The assertion.
|
|
*
|
|
* Returns:
|
|
* HTTP_BAD_REQUEST if we couldn't find the session id of the user, or
|
|
* OK if no error occured.
|
|
*/
|
|
static int add_attributes(am_cache_entry_t *session, request_rec *r,
|
|
const char *name_id, LassoSaml2Assertion *assertion)
|
|
{
|
|
am_dir_cfg_rec *dir_cfg;
|
|
GList *atr_stmt_itr;
|
|
LassoSaml2AttributeStatement *atr_stmt;
|
|
GList *atr_itr;
|
|
LassoSaml2Attribute *attribute;
|
|
GList *value_itr;
|
|
LassoSaml2AttributeValue *value;
|
|
LassoMiscTextNode *value_text;
|
|
int ret;
|
|
|
|
dir_cfg = am_get_dir_cfg(r);
|
|
|
|
/* Set expires to whatever is set by MellonSessionLength. */
|
|
if(dir_cfg->session_length == -1) {
|
|
/* -1 means "use default. The current default is 86400 seconds. */
|
|
am_cache_update_expires(session, apr_time_now()
|
|
+ apr_time_make(86400, 0));
|
|
} else {
|
|
am_cache_update_expires(session, apr_time_now()
|
|
+ apr_time_make(dir_cfg->session_length, 0));
|
|
}
|
|
|
|
/* Save session information. */
|
|
ret = am_cache_env_append(session, "NAME_ID", name_id);
|
|
if(ret != OK) {
|
|
return ret;
|
|
}
|
|
|
|
/* Update expires timestamp of session. */
|
|
am_handle_session_expire(r, session, assertion);
|
|
|
|
/* assertion->AttributeStatement is a list of
|
|
* LassoSaml2AttributeStatement objects.
|
|
*/
|
|
for(atr_stmt_itr = g_list_first(assertion->AttributeStatement);
|
|
atr_stmt_itr != NULL;
|
|
atr_stmt_itr = g_list_next(atr_stmt_itr)) {
|
|
|
|
atr_stmt = atr_stmt_itr->data;
|
|
if (!LASSO_IS_SAML2_ATTRIBUTE_STATEMENT(atr_stmt)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of AttributeStatement node.");
|
|
continue;
|
|
}
|
|
|
|
/* atr_stmt->Attribute is list of LassoSaml2Attribute objects. */
|
|
for(atr_itr = g_list_first(atr_stmt->Attribute);
|
|
atr_itr != NULL;
|
|
atr_itr = g_list_next(atr_itr)) {
|
|
|
|
attribute = atr_itr->data;
|
|
if (!LASSO_IS_SAML2_ATTRIBUTE(attribute)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of Attribute node.");
|
|
continue;
|
|
}
|
|
|
|
/* attribute->AttributeValue is a list of
|
|
* LassoSaml2AttributeValue objects.
|
|
*/
|
|
for(value_itr = g_list_first(attribute->AttributeValue);
|
|
value_itr != NULL;
|
|
value_itr = g_list_next(value_itr)) {
|
|
|
|
value = value_itr->data;
|
|
if (!LASSO_IS_SAML2_ATTRIBUTE_VALUE(value)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of AttributeValue node.");
|
|
continue;
|
|
}
|
|
|
|
/* value->any is a list with the child nodes of the
|
|
* AttributeValue element.
|
|
*
|
|
* We assume that the list contains a single text node.
|
|
*/
|
|
if(value->any == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"AttributeValue element was empty.");
|
|
continue;
|
|
}
|
|
|
|
/* Verify that this is a LassoMiscTextNode object. */
|
|
if(!LASSO_IS_MISC_TEXT_NODE(value->any->data)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"AttributeValue element contained an "
|
|
" element which wasn't a text node.");
|
|
continue;
|
|
}
|
|
|
|
value_text = LASSO_MISC_TEXT_NODE(value->any->data);
|
|
|
|
|
|
/* Decode and save the attribute. */
|
|
ret = am_store_attribute(r, session, attribute->Name,
|
|
value_text->content);
|
|
if(ret != OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* This function validates that the received assertion verify the security level configured by
|
|
* MellonAuthnContextClassRef directives
|
|
*/
|
|
static int am_validate_authn_context_class_ref(request_rec *r,
|
|
LassoSaml2Assertion *assertion) {
|
|
int i = 0;
|
|
LassoSaml2AuthnStatement *authn_statement = NULL;
|
|
LassoSaml2AuthnContext *authn_context = NULL;
|
|
am_dir_cfg_rec *dir_cfg;
|
|
apr_array_header_t *refs;
|
|
|
|
dir_cfg = am_get_dir_cfg(r);
|
|
refs = dir_cfg->authn_context_class_ref;
|
|
if (! refs->nelts)
|
|
return OK;
|
|
|
|
if (! assertion->AuthnStatement) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Missing AuthnStatement in assertion, returning BadRequest.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
/* we only consider the first AuthnStatement, I do not know of any idp
|
|
* sending more than one. */
|
|
authn_statement = g_list_first(assertion->AuthnStatement)->data;
|
|
if (! authn_statement->AuthnContext) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Missing AuthnContext in assertion, returning BadRequest.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
authn_context = authn_statement->AuthnContext;
|
|
if (! authn_context->AuthnContextClassRef) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Missing AuthnContextClassRef in assertion, returning Forbidden.");
|
|
return HTTP_FORBIDDEN;
|
|
}
|
|
for (i = 0; i < refs->nelts; i++) {
|
|
const char *ref = ((char **)refs->elts)[i];
|
|
if (strcmp(ref, authn_context->AuthnContextClassRef) == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"AuthnContextClassRef (%s) matches the "
|
|
"MellonAuthnContextClassRef directive, "
|
|
"access can be granted.",
|
|
authn_context->AuthnContextClassRef);
|
|
return OK;
|
|
}
|
|
}
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"AuthnContextClassRef (%s) does not match the "
|
|
"MellonAuthnContextClassRef directive, returning "
|
|
"Forbidden.",
|
|
authn_context->AuthnContextClassRef);
|
|
return HTTP_FORBIDDEN;
|
|
}
|
|
|
|
/* This function finishes handling of a login response after it has been parsed
|
|
* by the HTTP-POST or HTTP-Artifact handler.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The current request.
|
|
* LassoLogin *login The login object which has been initialized with the
|
|
* data we have received from the IdP.
|
|
* char *relay_state The RelayState parameter from the POST data or from
|
|
* the request url. This parameter is urlencoded, and
|
|
* this function will urldecode it in-place. Therefore it
|
|
* must be possible to overwrite the data.
|
|
*
|
|
* Returns:
|
|
* A HTTP status code which should be returned to the client.
|
|
*/
|
|
static int am_handle_reply_common(request_rec *r, LassoLogin *login,
|
|
char *relay_state, char *saml_response)
|
|
{
|
|
char *url;
|
|
char *chr;
|
|
const char *name_id;
|
|
LassoSamlp2Response *response;
|
|
LassoSaml2Assertion *assertion;
|
|
const char *in_response_to;
|
|
am_dir_cfg_rec *dir_cfg;
|
|
am_cache_entry_t *session;
|
|
int rc;
|
|
const char *idp;
|
|
|
|
url = am_reconstruct_url(r);
|
|
chr = strchr(url, '?');
|
|
if (! chr) {
|
|
chr = strchr(url, ';');
|
|
}
|
|
if (chr) {
|
|
*chr = '\0';
|
|
}
|
|
|
|
|
|
dir_cfg = am_get_dir_cfg(r);
|
|
|
|
if(LASSO_PROFILE(login)->nameIdentifier == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No acceptable name identifier found in"
|
|
" SAML 2.0 response.");
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
name_id = LASSO_SAML2_NAME_ID(LASSO_PROFILE(login)->nameIdentifier)
|
|
->content;
|
|
|
|
response = LASSO_SAMLP2_RESPONSE(LASSO_PROFILE(login)->response);
|
|
|
|
if (response->parent.Destination) {
|
|
if (strcmp(response->parent.Destination, url)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid Destination on Response. Should be: %s",
|
|
url);
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
if (g_list_length(response->Assertion) == 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No Assertion in response.");
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
if (g_list_length(response->Assertion) > 1) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"More than one Assertion in response.");
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
assertion = g_list_first(response->Assertion)->data;
|
|
if (!LASSO_IS_SAML2_ASSERTION(assertion)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Wrong type of Assertion node.");
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
rc = am_validate_subject(r, assertion, url);
|
|
if (rc != OK) {
|
|
lasso_login_destroy(login);
|
|
return rc;
|
|
}
|
|
|
|
rc = am_validate_conditions(r, assertion,
|
|
LASSO_PROVIDER(LASSO_PROFILE(login)->server)->ProviderID);
|
|
|
|
if (rc != OK) {
|
|
lasso_login_destroy(login);
|
|
return rc;
|
|
}
|
|
|
|
in_response_to = response->parent.InResponseTo;
|
|
|
|
|
|
if(in_response_to != NULL) {
|
|
/* This is SP-initiated login. Check that we have a cookie. */
|
|
if(am_cookie_get(r) == NULL) {
|
|
/* Missing cookie. */
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
|
|
"User has disabled cookies, or has lost"
|
|
" the cookie before returning from the SAML2"
|
|
" login server.");
|
|
if(dir_cfg->no_cookie_error_page != NULL) {
|
|
apr_table_setn(r->headers_out, "Location",
|
|
dir_cfg->no_cookie_error_page);
|
|
lasso_login_destroy(login);
|
|
return HTTP_SEE_OTHER;
|
|
} else {
|
|
/* Return 400 Bad Request when the user hasn't set a
|
|
* no-cookie error page.
|
|
*/
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check AuthnContextClassRef */
|
|
rc = am_validate_authn_context_class_ref(r, assertion);
|
|
if (rc != OK) {
|
|
lasso_login_destroy(login);
|
|
return rc;
|
|
}
|
|
|
|
/* Create a new session. */
|
|
session = am_new_request_session(r);
|
|
if(session == NULL) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
|
|
"am_new_request_session() failed");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
rc = add_attributes(session, r, name_id, assertion);
|
|
if(rc != OK) {
|
|
am_release_request_session(r, session);
|
|
lasso_login_destroy(login);
|
|
return rc;
|
|
}
|
|
|
|
/* If requested, save the IdP ProviderId */
|
|
if(dir_cfg->idpattr != NULL) {
|
|
idp = LASSO_PROFILE(login)->remote_providerID;
|
|
if(idp != NULL) {
|
|
rc = am_cache_env_append(session, dir_cfg->idpattr, idp);
|
|
if(rc != OK) {
|
|
am_release_request_session(r, session);
|
|
lasso_login_destroy(login);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = lasso_login_accept_sso(login);
|
|
if(rc < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unable to accept SSO message."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
am_release_request_session(r, session);
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
|
|
/* Save the profile state. */
|
|
rc = am_save_lasso_profile_state(r, session, LASSO_PROFILE(login),
|
|
saml_response);
|
|
if(rc != OK) {
|
|
am_release_request_session(r, session);
|
|
lasso_login_destroy(login);
|
|
return rc;
|
|
}
|
|
|
|
/* Mark user as logged in. */
|
|
session->logged_in = 1;
|
|
|
|
am_release_request_session(r, session);
|
|
lasso_login_destroy(login);
|
|
|
|
|
|
/* No RelayState - we don't know what to do. Use default login path. */
|
|
if(relay_state == NULL) {
|
|
dir_cfg = am_get_dir_cfg(r);
|
|
apr_table_setn(r->headers_out, "Location", dir_cfg->login_path);
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
rc = am_urldecode(relay_state);
|
|
if (rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Could not urldecode RelayState value.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Check for bad characters in RelayState. */
|
|
rc = am_check_url(r, relay_state);
|
|
if (rc != OK) {
|
|
return rc;
|
|
}
|
|
|
|
apr_table_setn(r->headers_out, "Location",
|
|
relay_state);
|
|
|
|
/* HTTP_SEE_OTHER should be a redirect where the browser doesn't repeat
|
|
* the POST data to the new page.
|
|
*/
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
|
|
/* This function handles responses to login requests received with the
|
|
* HTTP-POST binding.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* HTTP_SEE_OTHER on success, or an error on failure.
|
|
*/
|
|
static int am_handle_post_reply(request_rec *r)
|
|
{
|
|
int rc;
|
|
char *post_data;
|
|
char *saml_response;
|
|
LassoServer *server;
|
|
LassoLogin *login;
|
|
char *relay_state;
|
|
|
|
/* Make sure that this is a POST request. */
|
|
if(r->method_number != M_POST) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Exptected POST request for HTTP-POST endpoint."
|
|
" Got a %s request instead.", r->method);
|
|
|
|
/* According to the documentation for request_rec, a handler which
|
|
* doesn't handle a request method, should set r->allowed to the
|
|
* methods it handles, and return DECLINED.
|
|
* However, the default handler handles GET-requests, so for GET
|
|
* requests the handler should return HTTP_METHOD_NOT_ALLOWED.
|
|
*/
|
|
r->allowed = M_POST;
|
|
|
|
if(r->method_number == M_GET) {
|
|
return HTTP_METHOD_NOT_ALLOWED;
|
|
} else {
|
|
return DECLINED;
|
|
}
|
|
}
|
|
|
|
/* Read POST-data. */
|
|
rc = am_read_post_data(r, &post_data, NULL);
|
|
if (rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Error reading POST data.");
|
|
return rc;
|
|
}
|
|
|
|
/* Extract the SAMLResponse-field from the data. */
|
|
saml_response = am_extract_query_parameter(r->pool, post_data,
|
|
"SAMLResponse");
|
|
if (saml_response == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Could not find SAMLResponse field in POST data.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
rc = am_urldecode(saml_response);
|
|
if (rc != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
|
|
"Could not urldecode SAMLResponse value.");
|
|
return rc;
|
|
}
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL) {
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
login = lasso_login_new(server);
|
|
if (login == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Failed to initialize LassoLogin object.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Process login responce. */
|
|
rc = lasso_login_process_authn_response_msg(login, saml_response);
|
|
if (rc != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error processing authn response."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Extract RelayState parameter. */
|
|
relay_state = am_extract_query_parameter(r->pool, post_data,
|
|
"RelayState");
|
|
|
|
/* Finish handling the reply with the common handler. */
|
|
return am_handle_reply_common(r, login, relay_state, saml_response);
|
|
}
|
|
|
|
|
|
/* This function handles responses to login requests which use the
|
|
* HTTP-Artifact binding.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* HTTP_SEE_OTHER on success, or an error on failure.
|
|
*/
|
|
static int am_handle_artifact_reply(request_rec *r)
|
|
{
|
|
int rc;
|
|
LassoServer *server;
|
|
LassoLogin *login;
|
|
char *response;
|
|
char *relay_state;
|
|
|
|
/* Make sure that this is a GET request. */
|
|
if(r->method_number != M_GET) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Exptected GET request for the HTTP-Artifact endpoint."
|
|
" Got a %s request instead.", r->method);
|
|
|
|
/* According to the documentation for request_rec, a handler which
|
|
* doesn't handle a request method, should set r->allowed to the
|
|
* methods it handles, and return DECLINED.
|
|
* However, the default handler handles GET-requests, so for GET
|
|
* requests the handler should return HTTP_METHOD_NOT_ALLOWED.
|
|
* This endpoints handles GET requests, so it isn't necessary to
|
|
* check for method_number == M_GET.
|
|
*/
|
|
r->allowed = M_GET;
|
|
|
|
return DECLINED;
|
|
}
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL) {
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
login = lasso_login_new(server);
|
|
if (login == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Failed to initialize LassoLogin object.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Parse artifact url. */
|
|
rc = lasso_login_init_request(login, r->args,
|
|
LASSO_HTTP_METHOD_ARTIFACT_GET);
|
|
if(rc < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Failed to handle login response."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
lasso_login_destroy(login);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Prepare SOAP request. */
|
|
rc = lasso_login_build_request_msg(login);
|
|
if(rc < 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Failed to prepare SOAP message for HTTP-Artifact"
|
|
" resolution."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Do the SOAP request. */
|
|
rc = am_httpclient_post_str(
|
|
r,
|
|
LASSO_PROFILE(login)->msg_url,
|
|
LASSO_PROFILE(login)->msg_body,
|
|
"text/xml",
|
|
(void**)&response,
|
|
NULL
|
|
);
|
|
if(rc != OK) {
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
rc = lasso_login_process_response_msg(login, response);
|
|
if(rc != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Failed to handle HTTP-Artifact response data."
|
|
" Lasso error: [%i] %s", rc, lasso_strerror(rc));
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Extract the RelayState parameter. */
|
|
relay_state = am_extract_query_parameter(r->pool, r->args,
|
|
"RelayState");
|
|
|
|
/* Finish handling the reply with the common handler. */
|
|
return am_handle_reply_common(r, login, relay_state, "");
|
|
}
|
|
|
|
|
|
|
|
/* This function builds web form inputs for a saved POST request,
|
|
* in multipart/form-data format.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request
|
|
* const char *post_data The savec POST request
|
|
*
|
|
* Returns:
|
|
* The web form fragment, or NULL on failure.
|
|
*/
|
|
const char *am_post_mkform_multipart(request_rec *r, const char *post_data)
|
|
{
|
|
const char *mime_part;
|
|
const char *boundary;
|
|
char *l1;
|
|
char *post_form = "";
|
|
|
|
/* Replace CRLF by LF */
|
|
post_data = am_strip_cr(r, post_data);
|
|
|
|
if ((boundary = am_xstrtok(r, post_data, "\n", &l1)) == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Cannot figure initial boundary");
|
|
return NULL;
|
|
}
|
|
|
|
for (mime_part = am_xstrtok(r, post_data, boundary, &l1); mime_part;
|
|
mime_part = am_xstrtok(r, NULL, boundary, &l1)) {
|
|
const char *hdr;
|
|
const char *name = NULL;
|
|
const char *value = NULL;
|
|
const char *input_item;
|
|
|
|
/* End of MIME data */
|
|
if (strcmp(mime_part, "--\n") == 0)
|
|
break;
|
|
|
|
/* Remove leading CRLF */
|
|
if (strstr(mime_part, "\n") == mime_part)
|
|
mime_part += 1;
|
|
|
|
/* Empty part */
|
|
if (*mime_part == '\0')
|
|
continue;
|
|
|
|
/* Find Content-Disposition header
|
|
* Looking for
|
|
* Content-Disposition: form-data; name="the_name"\n
|
|
*/
|
|
hdr = am_get_mime_header(r, mime_part, "Content-Disposition");
|
|
if (hdr == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"No Content-Disposition header in MIME section,");
|
|
continue;
|
|
}
|
|
|
|
name = am_get_header_attr(r, hdr, "form-data", "name");
|
|
if (name == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unexpected Content-Disposition header: \"%s\"", hdr);
|
|
continue;
|
|
}
|
|
|
|
if ((value = am_get_mime_body(r, mime_part)) == NULL)
|
|
value = "";
|
|
|
|
input_item = apr_psprintf(r->pool,
|
|
" <input type=\"hidden\" name=\"%s\" value=\"%s\">\n",
|
|
am_htmlencode(r, name), am_htmlencode(r, value));
|
|
post_form = apr_pstrcat(r->pool, post_form, input_item, NULL);
|
|
}
|
|
|
|
return post_form;
|
|
}
|
|
|
|
/* This function builds web form inputs for a saved POST request,
|
|
* in application/x-www-form-urlencoded format
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request
|
|
* const char *post_data The savec POST request
|
|
*
|
|
* Returns:
|
|
* The web form fragment, or NULL on failure.
|
|
*/
|
|
const char *am_post_mkform_urlencoded(request_rec *r, const char *post_data)
|
|
{
|
|
const char *item;
|
|
char *last;
|
|
char *post_form = "";
|
|
|
|
for (item = am_xstrtok(r, post_data, "&", &last); item;
|
|
item = am_xstrtok(r, NULL, "&", &last)) {
|
|
char *l1;
|
|
char *name;
|
|
char *value;
|
|
const char *input_item;
|
|
|
|
name = (char *)am_xstrtok(r, item, "=", &l1);
|
|
value = (char *)am_xstrtok(r, NULL, "=", &l1);
|
|
|
|
if (name == NULL)
|
|
continue;
|
|
|
|
if (value == NULL)
|
|
value = (char *)"";
|
|
|
|
if (am_urldecode(name) != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"urldecode(\"%s\") failed", name);
|
|
return NULL;
|
|
}
|
|
|
|
if (am_urldecode(value) != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"urldecode(\"%s\") failed", value);
|
|
return NULL;
|
|
}
|
|
|
|
input_item = apr_psprintf(r->pool,
|
|
" <input type=\"hidden\" name=\"%s\" value=\"%s\">\n",
|
|
am_htmlencode(r, name), am_htmlencode(r, value));
|
|
post_form = apr_pstrcat(r->pool, post_form, input_item, NULL);
|
|
}
|
|
return post_form;
|
|
}
|
|
|
|
|
|
/* This function handles responses to repost request
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error on failure.
|
|
*/
|
|
static int am_handle_repost(request_rec *r)
|
|
{
|
|
am_mod_cfg_rec *mod_cfg;
|
|
const char *query;
|
|
const char *enctype;
|
|
char *charset;
|
|
char *psf_id;
|
|
char *cp;
|
|
char *psf_filename;
|
|
char *post_data;
|
|
const char *post_form;
|
|
char *output;
|
|
char *return_url;
|
|
const char *(*post_mkform)(request_rec *, const char *);
|
|
|
|
if (am_cookie_get(r) == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Repost query without a session");
|
|
return HTTP_FORBIDDEN;
|
|
}
|
|
|
|
mod_cfg = am_get_mod_cfg(r->server);
|
|
|
|
if (!mod_cfg->post_dir) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Repost query without MellonPostDirectory.");
|
|
return HTTP_NOT_FOUND;
|
|
}
|
|
|
|
query = r->parsed_uri.query;
|
|
|
|
enctype = am_extract_query_parameter(r->pool, query, "enctype");
|
|
if (enctype == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: missing enctype");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
if (strcmp(enctype, "urlencoded") == 0) {
|
|
enctype = "application/x-www-form-urlencoded";
|
|
post_mkform = am_post_mkform_urlencoded;
|
|
} else if (strcmp(enctype, "multipart") == 0) {
|
|
enctype = "multipart/form-data";
|
|
post_mkform = am_post_mkform_multipart;
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: invalid enctype \"%s\".", enctype);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
charset = am_extract_query_parameter(r->pool, query, "charset");
|
|
if (charset != NULL) {
|
|
if (am_urldecode(charset) != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: invalid charset \"%s\"", charset);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Check that charset is sane */
|
|
for (cp = charset; *cp; cp++) {
|
|
if (!apr_isalnum(*cp) && (*cp != '-') && (*cp != '_')) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: invalid charset \"%s\"", charset);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
}
|
|
|
|
psf_id = am_extract_query_parameter(r->pool, query, "id");
|
|
if (psf_id == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: missing id");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
/* Check that Id is sane */
|
|
for (cp = psf_id; *cp; cp++) {
|
|
if (!apr_isalnum(*cp)) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: invalid id \"%s\"", psf_id);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
}
|
|
|
|
|
|
return_url = am_extract_query_parameter(r->pool, query, "ReturnTo");
|
|
if (return_url == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid or missing query ReturnTo parameter.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
if (am_urldecode(return_url) != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Bad repost query: return");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
psf_filename = apr_psprintf(r->pool, "%s/%s", mod_cfg->post_dir, psf_id);
|
|
if ((post_data = am_getfile(r->pool, r->server, psf_filename)) == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Bad repost query: cannot find \"%s\"", psf_filename);
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
if ((post_form = (*post_mkform)(r, post_data)) == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "am_post_mkform() failed");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
if (charset != NULL) {
|
|
ap_set_content_type(r, apr_psprintf(r->pool,
|
|
"text/html; charset=\"%s\"", charset));
|
|
charset = apr_psprintf(r->pool, " accept-charset=\"%s\"", charset);
|
|
} else {
|
|
ap_set_content_type(r, "text/html");
|
|
charset = (char *)"";
|
|
}
|
|
|
|
output = apr_psprintf(r->pool,
|
|
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
|
|
"<html>\n"
|
|
" <head>\n"
|
|
" <title>SAML rePOST request</title>\n"
|
|
" </head>\n"
|
|
" <body onload=\"document.getElementById('form').submit();\">\n"
|
|
" <noscript>\n"
|
|
" Your browser does not support Javascript, \n"
|
|
" you must click the button below to proceed.\n"
|
|
" </noscript>\n"
|
|
" <form id=\"form\" method=\"POST\" action=\"%s\" enctype=\"%s\"%s>\n%s"
|
|
" <noscript>\n"
|
|
" <input type=\"submit\">\n"
|
|
" </noscript>\n"
|
|
" </form>\n"
|
|
" </body>\n"
|
|
"</html>\n",
|
|
am_htmlencode(r, return_url), enctype, charset, post_form);
|
|
|
|
ap_rputs(output, r);
|
|
return OK;
|
|
}
|
|
|
|
|
|
/* This function handles responses to metadata request
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error on failure.
|
|
*/
|
|
static int am_handle_metadata(request_rec *r)
|
|
{
|
|
#ifdef HAVE_lasso_server_new_from_buffers
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
LassoServer *server;
|
|
const char *data;
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL)
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
|
cfg = cfg->inherit_server_from;
|
|
|
|
data = cfg->sp_metadata_file;
|
|
if (data == NULL)
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
|
|
ap_set_content_type(r, "application/samlmetadata+xml");
|
|
|
|
ap_rputs(data, r);
|
|
|
|
return OK;
|
|
#else /* ! HAVE_lasso_server_new_from_buffers */
|
|
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"metadata publishing require lasso 2.2.2 or higher");
|
|
return HTTP_NOT_FOUND;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Send AuthnRequest using HTTP-Redirect binding.
|
|
*
|
|
* Note that this method frees the LassoLogin object.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r
|
|
* LassoLogin *login
|
|
*
|
|
* Returns:
|
|
* HTTP_SEE_OTHER on success, or an error on failure.
|
|
*/
|
|
static int am_send_authn_request_redirect(request_rec *r, LassoLogin *login)
|
|
{
|
|
char *redirect_to;
|
|
|
|
/* The URL we should send the message to. */
|
|
redirect_to = apr_pstrdup(r->pool, LASSO_PROFILE(login)->msg_url);
|
|
|
|
/* Check if the lasso library added the RelayState. If lasso didn't add
|
|
* a RelayState parameter, then we add one ourself. This should hopefully
|
|
* be removed in the future.
|
|
*/
|
|
if(strstr(redirect_to, "&RelayState=") == NULL
|
|
&& strstr(redirect_to, "?RelayState=") == NULL) {
|
|
/* The url didn't contain the relaystate parameter. */
|
|
redirect_to = apr_pstrcat(
|
|
r->pool, redirect_to, "&RelayState=",
|
|
am_urlencode(r->pool, LASSO_PROFILE(login)->msg_relayState),
|
|
NULL
|
|
);
|
|
}
|
|
apr_table_setn(r->headers_out, "Location", redirect_to);
|
|
|
|
lasso_login_destroy(login);
|
|
|
|
/* We don't want to include POST data (in case this was a POST request). */
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
/* Send AuthnRequest using HTTP-POST binding.
|
|
*
|
|
* Note that this method frees the LassoLogin object.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we are processing.
|
|
* LassoLogin *login The login message.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error on failure.
|
|
*/
|
|
static int am_send_authn_request_post(request_rec *r, LassoLogin *login)
|
|
{
|
|
char *url;
|
|
char *message;
|
|
char *relay_state;
|
|
char *output;
|
|
|
|
url = am_htmlencode(r, LASSO_PROFILE(login)->msg_url);
|
|
message = am_htmlencode(r, LASSO_PROFILE(login)->msg_body);
|
|
relay_state = am_htmlencode(r, LASSO_PROFILE(login)->msg_relayState);
|
|
|
|
lasso_login_destroy(login);
|
|
|
|
output = apr_psprintf(r->pool,
|
|
"<!DOCTYPE html>\n"
|
|
"<html>\n"
|
|
" <head>\n"
|
|
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
|
|
" <title>POST data</title>\n"
|
|
" </head>\n"
|
|
" <body onload=\"document.forms[0].submit()\">\n"
|
|
" <noscript><p>\n"
|
|
" <strong>Note:</strong> Since your browser does not support JavaScript, you must press the button below once to proceed.\n"
|
|
" </p></noscript>\n"
|
|
" <form method=\"POST\" action=\"%s\">\n"
|
|
" <input type=\"hidden\" name=\"SAMLRequest\" value=\"%s\">\n"
|
|
" <input type=\"hidden\" name=\"RelayState\" value=\"%s\">\n"
|
|
" <noscript>\n"
|
|
" <input type=\"submit\">\n"
|
|
" </noscript>\n"
|
|
" </form>\n"
|
|
" </body>\n"
|
|
"</html>\n",
|
|
url, message, relay_state);
|
|
|
|
ap_set_content_type(r, "text/html");
|
|
ap_rputs(output, r);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* Create and send an authentication request.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we are processing.
|
|
* const char *idp The entityID of the IdP.
|
|
* const char *return_to The URL we should redirect to when receiving the request.
|
|
* int is_passive Whether to send a passive request.
|
|
*
|
|
* Returns:
|
|
* HTTP response code indicating success or failure.
|
|
*/
|
|
static int am_send_authn_request(request_rec *r, const char *idp,
|
|
const char *return_to, int is_passive)
|
|
{
|
|
LassoServer *server;
|
|
LassoProvider *provider;
|
|
LassoLogin *login;
|
|
LassoSamlp2AuthnRequest *request;
|
|
LassoHttpMethod http_method;
|
|
char *sso_url;
|
|
gint ret;
|
|
am_dir_cfg_rec *dir_cfg;
|
|
|
|
dir_cfg = am_get_dir_cfg(r);
|
|
|
|
/* Add cookie for cookie test. We know that we should have
|
|
* a valid cookie when we return from the IdP after SP-initiated
|
|
* login.
|
|
*/
|
|
am_cookie_set(r, "cookietest");
|
|
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL) {
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Find our IdP. */
|
|
provider = lasso_server_get_provider(server, idp);
|
|
if (provider == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not find metadata for the IdP \"%s\".",
|
|
idp);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Determine what binding and endpoint we should use when
|
|
* sending the request.
|
|
*/
|
|
http_method = LASSO_HTTP_METHOD_REDIRECT;
|
|
sso_url = lasso_provider_get_metadata_one(
|
|
provider, "SingleSignOnService HTTP-Redirect");
|
|
if (sso_url == NULL) {
|
|
/* HTTP-Redirect unsupported - try HTTP-POST. */
|
|
http_method = LASSO_HTTP_METHOD_POST;
|
|
sso_url = lasso_provider_get_metadata_one(
|
|
provider, "SingleSignOnService HTTP-POST");
|
|
}
|
|
if (sso_url == NULL) {
|
|
/* Both HTTP-Redirect and HTTP-POST unsupported - give up. */
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Could not find a supported SingleSignOnService endpoint"
|
|
" for the IdP \"%s\".", idp);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
login = lasso_login_new(server);
|
|
if(login == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error creating LassoLogin object from LassoServer.");
|
|
g_free(sso_url);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
ret = lasso_login_init_authn_request(login, idp, http_method);
|
|
if(ret != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error creating login request."
|
|
" Lasso error: [%i] %s", ret, lasso_strerror(ret));
|
|
g_free(sso_url);
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
request = LASSO_SAMLP2_AUTHN_REQUEST(LASSO_PROFILE(login)->request);
|
|
if(request->NameIDPolicy == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error creating login request. Please verify the "
|
|
"MellonSPMetadataFile directive.");
|
|
g_free(sso_url);
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
request->ForceAuthn = FALSE;
|
|
request->IsPassive = is_passive;
|
|
|
|
request->NameIDPolicy->AllowCreate = TRUE;
|
|
|
|
LASSO_SAMLP2_REQUEST_ABSTRACT(request)->Consent
|
|
= g_strdup(LASSO_SAML2_CONSENT_IMPLICIT);
|
|
|
|
/* Add AuthnContextClassRef */
|
|
if (dir_cfg->authn_context_class_ref->nelts) {
|
|
apr_array_header_t *refs = dir_cfg->authn_context_class_ref;
|
|
int i = 0;
|
|
LassoSamlp2RequestedAuthnContext *req_authn_context;
|
|
|
|
req_authn_context = (LassoSamlp2RequestedAuthnContext*)
|
|
lasso_samlp2_requested_authn_context_new();
|
|
|
|
request->RequestedAuthnContext = req_authn_context;
|
|
|
|
for (i = 0; i < refs->nelts; i++) {
|
|
const char *ref = ((char **)refs->elts)[i];
|
|
req_authn_context->AuthnContextClassRef =
|
|
g_list_append(req_authn_context->AuthnContextClassRef,
|
|
g_strdup(ref));
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"adding AuthnContextClassRef %s to the "
|
|
"AuthnRequest", ref);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure the Destination attribute is set to the IdP
|
|
* SingleSignOnService endpoint. This is required for
|
|
* Shibboleth 2 interoperability, and older versions of
|
|
* lasso (at least up to 2.2.91) did not do it.
|
|
*/
|
|
if (LASSO_SAMLP2_REQUEST_ABSTRACT(request)->Destination == NULL) {
|
|
LASSO_SAMLP2_REQUEST_ABSTRACT(request)->Destination = g_strdup(sso_url);
|
|
}
|
|
|
|
/* sso_url no longer needed. */
|
|
g_free(sso_url);
|
|
|
|
LASSO_PROFILE(login)->msg_relayState = g_strdup(return_to);
|
|
|
|
ret = lasso_login_build_authn_request_msg(login);
|
|
if(ret != 0) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error building login request."
|
|
" Lasso error: [%i] %s", ret, lasso_strerror(ret));
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/* Time to actually send the authentication request. */
|
|
switch (http_method) {
|
|
case LASSO_HTTP_METHOD_REDIRECT:
|
|
return am_send_authn_request_redirect(r, login);
|
|
case LASSO_HTTP_METHOD_POST:
|
|
return am_send_authn_request_post(r, login);
|
|
default:
|
|
/* We should never get here. */
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Unsupported http_method.");
|
|
lasso_login_destroy(login);
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
}
|
|
|
|
|
|
/* Handle the "auth" endpoint.
|
|
*
|
|
* This endpoint is included for backwards-compatibility.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* OK or HTTP_SEE_OTHER on success, an error on failure.
|
|
*/
|
|
static int am_handle_auth(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
const char *relay_state;
|
|
|
|
relay_state = am_reconstruct_url(r);
|
|
|
|
/* Check if IdP discovery is in use and no IdP was selected yet */
|
|
if ((cfg->discovery_url != NULL) &&
|
|
(am_extract_query_parameter(r->pool, r->args, "IdP") == NULL)) {
|
|
return am_start_disco(r, relay_state);
|
|
}
|
|
|
|
/* If IdP discovery is in use and we have an IdP selected,
|
|
* set the relay_state
|
|
*/
|
|
if (cfg->discovery_url != NULL) {
|
|
char *return_url;
|
|
|
|
return_url = am_extract_query_parameter(r->pool, r->args, "ReturnTo");
|
|
if ((return_url != NULL) && am_urldecode((char *)return_url) == 0)
|
|
relay_state = return_url;
|
|
}
|
|
|
|
return am_send_authn_request(r, am_get_idp(r), relay_state, FALSE);
|
|
}
|
|
|
|
/* This function handles requests to the login handler.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error if any of the steps fail.
|
|
*/
|
|
static int am_handle_login(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
char *idp_param;
|
|
const char *idp;
|
|
char *return_to;
|
|
char *is_passive_str;
|
|
int is_passive;
|
|
int ret;
|
|
|
|
return_to = am_extract_query_parameter(r->pool, r->args, "ReturnTo");
|
|
if(return_to == NULL) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Missing required ReturnTo parameter.");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
|
|
ret = am_urldecode(return_to);
|
|
if(ret != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error urldecoding ReturnTo parameter.");
|
|
return ret;
|
|
}
|
|
|
|
idp_param = am_extract_query_parameter(r->pool, r->args, "IdP");
|
|
if(idp_param != NULL) {
|
|
ret = am_urldecode(idp_param);
|
|
if(ret != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error urldecoding IdP parameter.");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
is_passive_str = am_extract_query_parameter(r->pool, r->args, "IsPassive");
|
|
if(is_passive_str != NULL) {
|
|
ret = am_urldecode((char*)is_passive_str);
|
|
if(ret != OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Error urldecoding IsPassive parameter.");
|
|
return ret;
|
|
}
|
|
if(!strcmp(is_passive_str, "true")) {
|
|
is_passive = TRUE;
|
|
} else if(!strcmp(is_passive_str, "false")) {
|
|
is_passive = FALSE;
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Invalid value for IsPassive parameter - must be \"true\" or \"false\".");
|
|
return HTTP_BAD_REQUEST;
|
|
}
|
|
} else {
|
|
is_passive = FALSE;
|
|
}
|
|
|
|
if(idp_param != NULL) {
|
|
idp = idp_param;
|
|
} else if(cfg->discovery_url) {
|
|
if(is_passive) {
|
|
/* We cannot currently do discovery with passive authentication requests. */
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Discovery service with passive authentication request unsupported.");
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
return am_start_disco(r, return_to);
|
|
} else {
|
|
/* No discovery service -- just use the default IdP. */
|
|
idp = am_get_idp(r);
|
|
}
|
|
|
|
return am_send_authn_request(r, idp, return_to, is_passive);
|
|
}
|
|
|
|
/* This function probes an URL (HTTP GET)
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request.
|
|
* const char *url The URL
|
|
* int timeout Timeout in seconds
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error if any of the steps fail.
|
|
*/
|
|
static int am_probe_url(request_rec *r, const char *url, int timeout)
|
|
{
|
|
void *dontcare;
|
|
apr_size_t len;
|
|
long status;
|
|
int error;
|
|
|
|
status = 0;
|
|
if ((error = am_httpclient_get(r, url, &dontcare, &len,
|
|
timeout, &status)) != OK)
|
|
return error;
|
|
|
|
if (status != HTTP_OK) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
|
|
"Probe on \"%s\" returned HTTP %ld",
|
|
url, status);
|
|
return status;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* 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);
|
|
LassoServer *server;
|
|
const char *disco_idp = NULL;
|
|
int timeout;
|
|
char *return_to;
|
|
char *idp_param;
|
|
char *redirect_url;
|
|
int ret;
|
|
|
|
server = am_get_lasso_server(r);
|
|
if(server == NULL) {
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* First try sending probes to IdP configured for discovery.
|
|
* Second send probes for all configured IdP
|
|
* The first to answer is chosen.
|
|
* If none answer, use the first configured IdP
|
|
*/
|
|
if (!apr_is_empty_table(cfg->probe_discovery_idp)) {
|
|
const apr_array_header_t *header;
|
|
apr_table_entry_t *elts;
|
|
const char *url;
|
|
const char *idp;
|
|
int i;
|
|
|
|
header = apr_table_elts(cfg->probe_discovery_idp);
|
|
elts = (apr_table_entry_t *)header->elts;
|
|
|
|
for (i = 0; i < header->nelts; i++) {
|
|
idp = elts[i].key;
|
|
url = elts[i].val;
|
|
|
|
if (am_probe_url(r, url, timeout) == OK) {
|
|
disco_idp = idp;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
GList *iter;
|
|
GList *idp_list;
|
|
const char *idp;
|
|
|
|
idp_list = g_hash_table_get_keys(server->providers);
|
|
for (iter = idp_list; iter != NULL; iter = iter->next) {
|
|
idp = iter->data;
|
|
|
|
if (am_probe_url(r, idp, timeout) == OK) {
|
|
disco_idp = idp;
|
|
break;
|
|
}
|
|
}
|
|
g_list_free(idp_list);
|
|
}
|
|
|
|
/*
|
|
* On failure, try default
|
|
*/
|
|
if (disco_idp == NULL) {
|
|
disco_idp = am_first_idp(r);
|
|
if (disco_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", disco_idp);
|
|
}
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
|
|
"probeDiscovery using %s", disco_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, disco_idp));
|
|
|
|
apr_table_setn(r->headers_out, "Location", redirect_url);
|
|
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
|
|
/* This function handles responses to request on our endpoint
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* OK on success, or an error on failure.
|
|
*/
|
|
int am_handler(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
const char *endpoint;
|
|
|
|
/* Check if this is a request for one of our endpoints. We check if
|
|
* the uri starts with the path set with the MellonEndpointPath
|
|
* configuration directive.
|
|
*/
|
|
if(strstr(r->uri, cfg->endpoint_path) != r->uri)
|
|
return DECLINED;
|
|
|
|
endpoint = &r->uri[strlen(cfg->endpoint_path)];
|
|
if (!strcmp(endpoint, "metadata")) {
|
|
return am_handle_metadata(r);
|
|
} else if (!strcmp(endpoint, "repost")) {
|
|
return am_handle_repost(r);
|
|
} else if(!strcmp(endpoint, "postResponse")) {
|
|
return am_handle_post_reply(r);
|
|
} else if(!strcmp(endpoint, "artifactResponse")) {
|
|
return am_handle_artifact_reply(r);
|
|
} else if(!strcmp(endpoint, "auth")) {
|
|
return am_handle_auth(r);
|
|
} else if(!strcmp(endpoint, "logout")
|
|
|| !strcmp(endpoint, "logoutRequest")) {
|
|
/* logoutRequest is included for backwards-compatibility
|
|
* with version 0.0.6 and older.
|
|
*/
|
|
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.",
|
|
endpoint);
|
|
|
|
return HTTP_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Trigger a login operation from a "normal" request.
|
|
*
|
|
* Parameters:
|
|
* request_rec *r The request we received.
|
|
*
|
|
* Returns:
|
|
* HTTP_SEE_OTHER on success, or an error on failure.
|
|
*/
|
|
static int am_start_auth(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *cfg = am_get_dir_cfg(r);
|
|
const char *endpoint = am_get_endpoint_url(r);
|
|
const char *return_to;
|
|
const char *idp;
|
|
const char *login_url;
|
|
|
|
return_to = am_reconstruct_url(r);
|
|
|
|
/* If this is a POST request, attempt to save it */
|
|
if (r->method_number == M_POST) {
|
|
if (CFG_VALUE(cfg, post_replay)) {
|
|
if (am_save_post(r, &return_to) != OK)
|
|
return HTTP_INTERNAL_SERVER_ERROR;
|
|
} else {
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"POST data dropped because we do not have a"
|
|
" MellonPostReplay is not enabled.");
|
|
}
|
|
}
|
|
|
|
/* Check if IdP discovery is in use. */
|
|
if (cfg->discovery_url) {
|
|
return am_start_disco(r, return_to);
|
|
}
|
|
|
|
idp = am_get_idp(r);
|
|
login_url = apr_psprintf(r->pool, "%slogin?ReturnTo=%s&IdP=%s",
|
|
endpoint,
|
|
am_urlencode(r->pool, return_to),
|
|
am_urlencode(r->pool, idp));
|
|
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
|
|
"Redirecting to login URL: %s", login_url);
|
|
|
|
apr_table_setn(r->headers_out, "Location", login_url);
|
|
return HTTP_SEE_OTHER;
|
|
}
|
|
|
|
int am_auth_mellon_user(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *dir = am_get_dir_cfg(r);
|
|
int return_code = HTTP_UNAUTHORIZED;
|
|
am_cache_entry_t *session;
|
|
|
|
/* check if we are a subrequest. if we are, then just return OK
|
|
* without any checking since these cannot be injected (heh). */
|
|
if (r->main)
|
|
return OK;
|
|
|
|
/* Check that the user has enabled authentication for this directory. */
|
|
if(dir->enable_mellon == am_enable_off
|
|
|| dir->enable_mellon == am_enable_default) {
|
|
return DECLINED;
|
|
}
|
|
|
|
/* Disable all caching within this location. */
|
|
am_set_nocache(r);
|
|
|
|
/* Check if this is a request for one of our endpoints. We check if
|
|
* the uri starts with the path set with the MellonEndpointPath
|
|
* configuration directive.
|
|
*/
|
|
if(strstr(r->uri, dir->endpoint_path) == r->uri) {
|
|
/* No access control on our internal endpoints. */
|
|
return OK;
|
|
}
|
|
|
|
/* Get the session of this request. */
|
|
session = am_get_request_session(r);
|
|
|
|
|
|
if(dir->enable_mellon == am_enable_auth) {
|
|
/* This page requires the user to be authenticated and authorized. */
|
|
|
|
if(session == NULL || !session->logged_in) {
|
|
/* We don't have a valid session. */
|
|
|
|
if(session) {
|
|
/* Release the session. */
|
|
am_release_request_session(r, session);
|
|
}
|
|
|
|
/* Send the user to the authentication page on the IdP. */
|
|
return am_start_auth(r);
|
|
}
|
|
|
|
/* Verify that the user has access to this resource. */
|
|
return_code = am_check_permissions(r, session);
|
|
if(return_code != OK) {
|
|
am_release_request_session(r, session);
|
|
|
|
return return_code;
|
|
}
|
|
|
|
|
|
/* The user has been authenticated, and we can now populate r->user
|
|
* and the r->subprocess_env with values from the session store.
|
|
*/
|
|
am_cache_env_populate(r, session);
|
|
|
|
/* Release the session. */
|
|
am_release_request_session(r, session);
|
|
|
|
return OK;
|
|
|
|
} else {
|
|
/* dir->enable_mellon == am_enable_info:
|
|
* We should pass information about the user to the web application
|
|
* if the user is authorized to access this resource.
|
|
* However, we shouldn't attempt to do any access control.
|
|
*/
|
|
|
|
if(session != NULL
|
|
&& session->logged_in
|
|
&& am_check_permissions(r, session) == OK) {
|
|
|
|
/* The user is authenticated and has access to the resource.
|
|
* Now we populate the environment with information about
|
|
* the user.
|
|
*/
|
|
am_cache_env_populate(r, session);
|
|
}
|
|
|
|
if(session != NULL) {
|
|
/* Release the session. */
|
|
am_release_request_session(r, session);
|
|
}
|
|
|
|
/* We shouldn't really do any access control, so we always return
|
|
* DECLINED.
|
|
*/
|
|
return DECLINED;
|
|
}
|
|
}
|
|
|
|
|
|
int am_check_uid(request_rec *r)
|
|
{
|
|
am_dir_cfg_rec *dir = am_get_dir_cfg(r);
|
|
am_cache_entry_t *session;
|
|
int return_code = HTTP_UNAUTHORIZED;
|
|
|
|
/* check if we are a subrequest. if we are, then just return OK
|
|
* without any checking since these cannot be injected (heh). */
|
|
if (r->main)
|
|
return OK;
|
|
|
|
/* Check if this is a request for one of our endpoints. We check if
|
|
* the uri starts with the path set with the MellonEndpointPath
|
|
* configuration directive.
|
|
*/
|
|
if(strstr(r->uri, dir->endpoint_path) == r->uri) {
|
|
/* No access control on our internal endpoints. */
|
|
return OK;
|
|
}
|
|
|
|
|
|
/* Get the session of this request. */
|
|
session = am_get_request_session(r);
|
|
|
|
/* If we don't have a session, then we can't authorize the user. */
|
|
if(session == NULL) {
|
|
return HTTP_UNAUTHORIZED;
|
|
}
|
|
|
|
/* If the user isn't logged in, then we can't authorize the user. */
|
|
if(!session->logged_in) {
|
|
am_release_request_session(r, session);
|
|
return HTTP_UNAUTHORIZED;
|
|
}
|
|
|
|
/* Verify that the user has access to this resource. */
|
|
return_code = am_check_permissions(r, session);
|
|
if(return_code != OK) {
|
|
am_release_request_session(r, session);
|
|
return HTTP_UNAUTHORIZED;
|
|
}
|
|
|
|
/* The user has been authenticated, and we can now populate r->user
|
|
* and the r->subprocess_env with values from the session store.
|
|
*/
|
|
am_cache_env_populate(r, session);
|
|
|
|
/* Release the session. */
|
|
am_release_request_session(r, session);
|
|
|
|
return OK;
|
|
}
|