modmellon/auth_mellon_handler.c

3341 lines
107 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 = "";
const char *sp_entity_id;
sp_entity_id = cfg->sp_entity_id ? cfg->sp_entity_id : url;
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=\"%s%s\"\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>",
sp_entity_id, cfg->sp_entity_id ? "" : "metadata",
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)
{
apr_size_t index;
#ifndef HAVE_lasso_server_load_metadata
const char *idp_public_key_file;
if (cfg->idp_metadata->nelts == 1)
idp_public_key_file = cfg->idp_public_key_file;
else
idp_public_key_file = NULL;
#endif /* ! HAVE_lasso_server_load_metadata */
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) {
for (GList *idx = loaded_idp; idx != NULL; idx = idx->next) {
g_free(idx->data);
}
g_list_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 = NULL;
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.
*/
if (session != NULL && res != LASSO_PROFILE_ERROR_SESSION_NOT_FOUND) {
/* We found a matching session -- delete it. */
am_delete_request_session(r, session);
session = NULL;
}
/* 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:
if (session != NULL) {
am_release_request_session(r, session);
}
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, am_compat_request_ip(r))) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Wrong Address in SubjectConfirmationData."
"Current address is \"%s\", but should have been \"%s\".",
am_compat_request_ip(r), 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 || strlen(relay_state) == 0) {
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;
am_dir_cfg_rec *dir_cfg = am_get_dir_cfg(r);
int i, err;
/* Make sure that this is a POST request. */
if(r->method_number != M_POST) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Expected 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);
err = HTTP_BAD_REQUEST;
for (i = 0; auth_mellon_errormap[i].lasso_error != 0; i++) {
if (auth_mellon_errormap[i].lasso_error == rc) {
err = auth_mellon_errormap[i].http_error;
break;
}
}
if (err == HTTP_UNAUTHORIZED) {
if (dir_cfg->no_success_error_page != NULL) {
apr_table_setn(r->headers_out, "Location",
dir_cfg->no_success_error_page);
return HTTP_SEE_OTHER;
}
}
return err;
}
/* 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;
char *saml_art;
char *post_data;
/* Make sure that this is a GET request. */
if(r->method_number != M_GET && r->method_number != M_POST) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Expected GET or POST 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. */
if (r->method_number == M_GET) {
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;
}
} else {
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_BAD_REQUEST;
}
saml_art = am_extract_query_parameter(r->pool, post_data, "SAMLart");
if (saml_art == NULL) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
"Error reading POST data missing SAMLart form parameter.");
return HTTP_BAD_REQUEST;
}
ap_unescape_url(saml_art);
rc = lasso_login_init_request(login, saml_art, LASSO_HTTP_METHOD_ARTIFACT_POST);
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. */
if (r->method_number == M_GET) {
relay_state = am_extract_query_parameter(r->pool, r->args,
"RelayState");
} else {
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, "");
}
/* 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);
post_data = am_getfile(r->pool, r->server, psf_filename);
if (post_data == NULL) {
/* Unable to load repost data. Just redirect us instead. */
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
"Bad repost query: cannot find \"%s\"", psf_filename);
apr_table_setn(r->headers_out, "Location", return_url);
return HTTP_SEE_OTHER;
}
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;
char *acs_url;
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);
/* Some IdPs insist they want to see an AttributeConsumerServiceURL
* attribute in the authentication request, so try to add one if the
* metadata contains one */
acs_url = lasso_provider_get_assertion_consumer_service_url(
LASSO_PROVIDER(server), NULL);
if (acs_url) {
request->AssertionConsumerServiceURL = g_strdup(acs_url);
/* Can't set request->ProtocolBinding (which is usually set along side
* AssertionConsumerServiceURL) as there is no immediate function
* like lasso_provider_get_assertion_consumer_service_url to get them.
* So leave that empty for now, it is not strictly required */
}
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;
}
/* Set defaut Cache-Control headers within this location */
am_set_cache_control_headers(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;
}