Skip to content

Commit

Permalink
added CASValidateRedirectTicket Parameter to allow bypassing the 302 …
Browse files Browse the repository at this point in the history
…redirect after ticket validation. This allows CAS enabled web services without the clients required to handle a 302 redirect.
  • Loading branch information
ruckc committed Jan 14, 2013
1 parent 275cf65 commit 9cc9f9f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 58 deletions.
6 changes: 6 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ Default: NULL
Description: The URL to use when performing a proxy validation. This is currently
an unimplemented feature, so setting this will have no effect.

Directive: CASValidateRedirectTicket
Default: On
Description: When disabled, Off, don't send 302 redirect to the client. Just allows
the request through with the ticket parameter. For use with CAS enabled
web services.

Directive: CASRootProxiedAs
Default: NULL
Description: This URL represents the URL that end users may see in the event that
Expand Down
139 changes: 82 additions & 57 deletions src/mod_auth_cas.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ void *cas_create_dir_config(apr_pool_t *pool, char *path)
c->CASSecureCookie = CAS_DEFAULT_SCOOKIE;
c->CASGatewayCookie = CAS_DEFAULT_GATEWAY_COOKIE;
c->CASAuthNHeader = CAS_DEFAULT_AUTHN_HEADER;
c->CASValidateRedirectTicket = CAS_DEFAULT_VALIDATE_REDIRECT_TICKET;
c->CASScrubRequestHeaders = CAS_DEFAULT_SCRUB_REQUEST_HEADERS;
return(c);
}
Expand Down Expand Up @@ -206,6 +207,8 @@ void *cas_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD)
add->CASAuthNHeader : base->CASAuthNHeader);
if (add->CASAuthNHeader != NULL && apr_strnatcasecmp(add->CASAuthNHeader, "Off") == 0)
c->CASAuthNHeader = NULL;

c->CASValidateRedirectTicket = (add->CASValidateRedirectTicket != CAS_DEFAULT_VALIDATE_REDIRECT_TICKET) ? add->CASValidateRedirectTicket : base->CASValidateRedirectTicket;

c->CASScrubRequestHeaders = (add->CASScrubRequestHeaders != CAS_DEFAULT_SCRUB_REQUEST_HEADERS ?
add->CASScrubRequestHeaders :
Expand All @@ -216,6 +219,13 @@ void *cas_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD)
return(c);
}

const char *cfg_read_validateredirectticket(cmd_parms *cmd, void *cfg, int flag)
{
cas_dir_cfg *c = (cas_dir_cfg *) cfg;
c->CASValidateRedirectTicket = flag;
return NULL;
}

const char *cfg_readCASParameter(cmd_parms *cmd, void *cfg, const char *value)
{
cas_cfg *c = (cas_cfg *) ap_get_module_config(cmd->server->module_config, &auth_cas_module);
Expand Down Expand Up @@ -1537,6 +1547,7 @@ apr_byte_t isValidCASTicket(request_rec *r, cas_cfg *c, char *ticket, char **use

apr_byte_t isValidCASCookie(request_rec *r, cas_cfg *c, char *cookie, char **user, cas_saml_attr **attrs)
{
char *path;
cas_cache_entry cache;
cas_dir_cfg *d = ap_get_module_config(r->per_dir_config, &auth_cas_module);

Expand All @@ -1550,6 +1561,8 @@ apr_byte_t isValidCASCookie(request_rec *r, cas_cfg *c, char *cookie, char **use
return FALSE;
}

path = apr_psprintf(r->pool, "%s%s", c->CASCookiePath, cookie);

/*
* mitigate session hijacking by not allowing cookies transmitted in the clear to be submitted
* for HTTPS URLs and by voiding HTTPS cookies sent in the clear
Expand Down Expand Up @@ -1709,6 +1722,9 @@ char *getResponseFromServer (request_rec *r, cas_cfg *c, char *ticket)
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);

memcpy(&validateURL, &c->CASValidateURL, sizeof(apr_uri_t));
if(c->CASDebug)
ap_log_rerror(APLOG_MARK,APLOG_DEBUG,0,r,"MOD_AUTH_CAS: validateUrl: %s", apr_uri_unparse(r->pool, &validateURL, 0));

if(c->CASValidateSAML == FALSE)
validateURL.query = apr_psprintf(r->pool, "service=%s&ticket=%s%s", getCASService(r, c), ticket, getCASRenew(r));
else
Expand All @@ -1725,8 +1741,11 @@ char *getResponseFromServer (request_rec *r, cas_cfg *c, char *ticket)
if(headers != NULL)
curl_slist_free_all(headers);

if(c->CASDebug)
/* TODO: Switch ERR to DEBUG */
if(c->CASDebug) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Query: %s", validateURL.query);
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Validation response: %s", curlBuffer.buf);
}

rv = apr_pstrndup(r->pool, curlBuffer.buf, strlen(curlBuffer.buf));

Expand Down Expand Up @@ -1988,31 +2007,34 @@ int cas_authenticate(request_rec *r)
if(d->CASAuthNHeader != NULL)
apr_table_set(r->headers_in, d->CASAuthNHeader, remoteUser);

if(parametersRemoved == TRUE) {
if(ssl == TRUE && port != 443)
printPort = TRUE;
else if(port != 80)
printPort = TRUE;

if(c->CASRootProxiedAs.is_initialized) {
newLocation = apr_psprintf(r->pool, "%s%s%s%s", apr_uri_unparse(r->pool, &c->CASRootProxiedAs, 0), r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
if(d->CASValidateRedirectTicket == TRUE) {
if(parametersRemoved == TRUE) {
if(ssl == TRUE && port != 443)
printPort = TRUE;
else if(port != 80)
printPort = TRUE;

if(c->CASRootProxiedAs.is_initialized) {
newLocation = apr_psprintf(r->pool, "%s%s%s%s", apr_uri_unparse(r->pool, &c->CASRootProxiedAs, 0), r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
} else {
#ifdef APACHE2_0
if(printPort == TRUE)
newLocation = apr_psprintf(r->pool, "%s://%s:%u%s%s%s", ap_http_method(r), r->server->server_hostname, r->connection->local_addr->port, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
else
newLocation = apr_psprintf(r->pool, "%s://%s%s%s%s", ap_http_method(r), r->server->server_hostname, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
#else
if(printPort == TRUE)
newLocation = apr_psprintf(r->pool, "%s://%s:%u%s%s%s", ap_http_scheme(r), r->server->server_hostname, r->connection->local_addr->port, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
else
newLocation = apr_psprintf(r->pool, "%s://%s%s%s%s", ap_http_scheme(r), r->server->server_hostname, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
#endif
}
ap_log_rerror(APLOG_MARK,APLOG_DEBUG,0,r,"Sending 302 redirect (%s)", newLocation);
apr_table_add(r->headers_out, "Location", newLocation);
return HTTP_MOVED_TEMPORARILY;
} else {
#ifdef APACHE2_0
if(printPort == TRUE)
newLocation = apr_psprintf(r->pool, "%s://%s:%u%s%s%s", ap_http_method(r), r->server->server_hostname, r->connection->local_addr->port, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
else
newLocation = apr_psprintf(r->pool, "%s://%s%s%s%s", ap_http_method(r), r->server->server_hostname, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
#else
if(printPort == TRUE)
newLocation = apr_psprintf(r->pool, "%s://%s:%u%s%s%s", ap_http_scheme(r), r->server->server_hostname, r->connection->local_addr->port, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
else
newLocation = apr_psprintf(r->pool, "%s://%s%s%s%s", ap_http_scheme(r), r->server->server_hostname, r->uri, ((r->args != NULL) ? "?" : ""), ((r->args != NULL) ? r->args : ""));
#endif
return OK;
}
apr_table_add(r->headers_out, "Location", newLocation);
return HTTP_MOVED_TEMPORARILY;
} else {
return OK;
}
} else {
/* sometimes, pages that automatically refresh will re-send the ticket parameter, so let's check any cookies presented or return an error if none */
Expand Down Expand Up @@ -2174,37 +2196,38 @@ int cas_match_attribute(const char *const attr_spec, const cas_saml_attr *const
}
}
}
/* The match is a success is we walked the whole attribute
* name and the attr_spec is a tilde (denotes a PCRE match). */
else if (!(*attr_c) && (*spec_c) == '~') {
const cas_saml_attr_val *val;
const char *errorptr;
int erroffset;
pcre *preg;
/* The match is a success is we walked the whole attribute
* name and the attr_spec is a tilde (denotes a PCRE match). */
else if (!(*attr_c) && (*spec_c) == '~') {
const cas_saml_attr_val *val;
const char *errorptr;
int erroffset;
pcre *preg;

/* Skip the tilde */
spec_c++;

/* Set up the regex */
preg = pcre_compile(spec_c, 0, &errorptr, &erroffset, NULL);
if (NULL == preg) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Pattern [%s] is not a valid regular expression", spec_c);
continue;
}

/* Compare the attribute values */
val = attr->values;
for ( ; val; val = val->next) {
/* PCRE-compare the attribute value. At this point, spec_c
* points to the NULL-terminated value pattern. */
if (0 == pcre_exec(preg, NULL, val->value, (int)strlen(val->value), 0, 0, NULL, 0)) {
pcre_free(preg);
return CAS_ATTR_MATCH;
}
}

pcre_free(preg);
}

/* Skip the tilde */
spec_c++;

/* Set up the regex */
preg = pcre_compile(spec_c, 0, &errorptr, &erroffset, NULL);
if (NULL == preg) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Pattern [%s] is not a valid regular expression", spec_c);
continue;
}

/* Compare the attribute values */
val = attr->values;
for ( ; val; val = val->next) {
/* PCRE-compare the attribute value. At this point, spec_c
* points to the NULL-terminated value pattern. */
if (0 == pcre_exec(preg, NULL, val->value, (int)strlen(val->value), 0, 0, NULL, 0)) {
pcre_free(preg);
return CAS_ATTR_MATCH;
}
}

pcre_free(preg);
}
}
return CAS_ATTR_NO_MATCH;
}
Expand Down Expand Up @@ -2300,7 +2323,7 @@ int cas_authorize_worker(request_rec *r, const cas_saml_attr *const attrs, const
"Require cas-attribute "
"'%s' matched", token);
return OK;
}
}
}
}

Expand All @@ -2314,7 +2337,7 @@ int cas_authorize_worker(request_rec *r, const cas_saml_attr *const attrs, const
return DECLINED;
}
/* If there was a "Require cas-attribute", but no actual attributes,
* that's cause to warn the admin of an iffy configuration.
* that's cause to warn the admin of an iffy configuration.
*/
if (count_casattr == 0) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
Expand Down Expand Up @@ -2572,11 +2595,12 @@ void cas_register_hooks(apr_pool_t *p)
AP_AUTH_INTERNAL_PER_URI);
#else
ap_hook_check_user_id(cas_authenticate, NULL, NULL, APR_HOOK_MIDDLE);
#endif
#endif
ap_hook_auth_checker(cas_authorize, NULL, authzSucc, APR_HOOK_MIDDLE);
ap_register_input_filter("CAS", cas_in_filter, NULL, AP_FTYPE_RESOURCE);
}


const command_rec cas_cmds [] = {
AP_INIT_TAKE1("CASVersion", cfg_readCASParameter, (void *) cmd_version, RSRC_CONF, "Set CAS Protocol Version (1 or 2)"),
AP_INIT_TAKE1("CASDebug", cfg_readCASParameter, (void *) cmd_debug, RSRC_CONF, "Enable or disable debug mode (On or Off)"),
Expand All @@ -2585,6 +2609,7 @@ const command_rec cas_cmds [] = {
AP_INIT_TAKE1("CASRenew", ap_set_string_slot, (void *) APR_OFFSETOF(cas_dir_cfg, CASRenew), ACCESS_CONF|OR_AUTHCFG, "Force credential renew (/app/secure/ will require renew on /app/secure/*)"),
AP_INIT_TAKE1("CASGateway", ap_set_string_slot, (void *) APR_OFFSETOF(cas_dir_cfg, CASGateway), ACCESS_CONF|OR_AUTHCFG, "Allow anonymous access if no CAS session is established on this path (e.g. /app/insecure/ will allow gateway access to /app/insecure/*), CAS v2 only"),
AP_INIT_TAKE1("CASAuthNHeader", ap_set_string_slot, (void *) APR_OFFSETOF(cas_dir_cfg, CASAuthNHeader), ACCESS_CONF|OR_AUTHCFG, "Specify the HTTP header variable to set with the name of the CAS authenticated user. By default no headers are added."),
AP_INIT_FLAG("CASValidateRedirectTicket", cfg_read_validateredirectticket, NULL, OR_AUTHCFG, "Default on, disable to not force 302 redirect on validation"),
AP_INIT_TAKE1("CASSSOEnabled", cfg_readCASParameter, (void *) cmd_sso, RSRC_CONF, "Enable or disable Single Sign Out functionality (On or Off)"),
AP_INIT_TAKE1("CASAttributeDelimiter", cfg_readCASParameter, (void *) cmd_attribute_delimiter, RSRC_CONF, "The delimiter to use when setting multi-valued attributes in the HTTP headers"),
AP_INIT_TAKE1("CASAttributePrefix", cfg_readCASParameter, (void *) cmd_attribute_prefix, RSRC_CONF, "The prefix to use when setting attributes in the HTTP headers"),
Expand Down
5 changes: 4 additions & 1 deletion src/mod_auth_cas.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
#define CAS_DEFAULT_SCOOKIE "MOD_AUTH_CAS_S"
#define CAS_DEFAULT_GATEWAY_COOKIE "MOD_CAS_G"
#define CAS_DEFAULT_AUTHN_HEADER NULL
#define CAS_DEFAULT_VALIDATE_REDIRECT_TICKET TRUE
#define CAS_DEFAULT_SCRUB_REQUEST_HEADERS NULL
#define CAS_DEFAULT_SSO_ENABLED FALSE
#define CAS_DEFAULT_AUTHORITATIVE FALSE
Expand Down Expand Up @@ -133,6 +134,7 @@ typedef struct cas_cfg {
} cas_cfg;

typedef struct cas_dir_cfg {
unsigned int CASValidateRedirectTicket;
char *CASScope;
char *CASRenew;
char *CASGateway;
Expand Down Expand Up @@ -164,7 +166,7 @@ typedef enum {
cmd_ca_path, cmd_cookie_path, cmd_loginurl, cmd_validateurl, cmd_proxyurl, cmd_cookie_entropy,
cmd_session_timeout, cmd_idle_timeout, cmd_cache_interval, cmd_cookie_domain, cmd_cookie_httponly,
cmd_sso, cmd_validate_saml, cmd_attribute_delimiter, cmd_attribute_prefix, cmd_root_proxied_as,
cmd_authoritative
cmd_authoritative, cmd_validredirticket
} valid_cmds;

module AP_MODULE_DECLARE_DATA auth_cas_module;
Expand All @@ -174,6 +176,7 @@ void *cas_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD);
void *cas_create_dir_config(apr_pool_t *pool, char *path);
void *cas_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD);
const char *cfg_readCASParameter(cmd_parms *cmd, void *cfg, const char *value);
const char *cfg_read_validateredirectticket(cmd_parms *cmd, void *cfg, int flag);
char *getResponseFromServer (request_rec *r, cas_cfg *c, char *ticket);
apr_byte_t validCASTicketFormat(const char *ticket);
apr_byte_t isValidCASTicket(request_rec *r, cas_cfg *c, char *ticket, char **user, cas_saml_attr **attrs);
Expand Down

0 comments on commit 9cc9f9f

Please sign in to comment.