New MellonCond directive to enable attribute filtering beyond MellonRequire

functionalities. Supports regexp, negations, and attribute name remapping
though MellonSetEnv



git-svn-id: https://modmellon.googlecode.com/svn/trunk/mod_mellon2@114 a716ebb1-153a-0410-b759-cfb97c6a1b53
This commit is contained in:
manu@netbsd.org 2011-03-17 05:20:40 +00:00
parent 7cb7b968e3
commit 4a774de00c
5 changed files with 332 additions and 54 deletions

3
NEWS
View File

@ -6,6 +6,9 @@ Version 0.3.1
* A /mellon/probeDisco endpoint replaces the builtin:get-metadata
IdP dicovery URL scheme
* New MellonCond directive to enable attribute filtering beyond
MellonRequire functionalities.
Version 0.3.0
---------------------------------------------------------------------------

45
README
View File

@ -152,9 +152,9 @@ MellonPostCount 100
# "auth": We will populate the environment with information about
# the user if he is authorized. If he is authenticated
# (logged in), but not authorized (according to the
# MellonRequire directives, then we will return a 403
# Forbidden error. If he isn't authenticated then we will
# redirect him to the login page of the IdP.
# MellonRequire and MellonCond directives, then we will
# return a 403 Forbidden error. If he isn't authenticated
# then we will redirect him to the login page of the IdP.
#
# Default: MellonEnable "off"
MellonEnable "auth"
@ -221,14 +221,45 @@ MellonPostCount 100
# Note that the attribute name is the name we received from the
# IdP.
#
# If you don't list any MellonRequire directives, then any user
# authenticated by the IdP will have access to this service. If
# you list several MellonRequire directives, then all of them
# will have to match.
# If you don't list any MellonRequire directives (and any
# MellonCond directives, see below), then any user authenticated
# by the IdP will have access to this service. If you list several
# MellonRequire directives, then all of them will have to match.
# If you use multiple MellonRequire directive on the same
# attribute, the last overrides the previous ones.
#
# Default: None set.
MellonRequire "eduPersonAffiliation" "student" "employee"
# MellonCond provides the same function as MellonRequire, with
# extra functionnality (MellonRequire is retained for backward
# compatibility). The syntax is
# 'MellonCond <attribute name> <value> [<options>]'
#
# <options> is an optional, comma-separated list of option
# encloseed with brackets. Here is an example: [NOT,NC]
# The valid options are:
# OR If this MellonCond evaluted to false, then the
# next one will be checked. If it evalutes to true,
# then the overall check succeeds.
# NOT This MellonCond evaluates to true if the attribute
# does not match the value.
# REG Value to check is a regular expression.
# NC Perform case insensitive match.
# MAP Attempt to search an attribute with name remapped by
# MellonSetEnv. Fallback to non remapped name if not
# found.
#
# It is allowed to have multiple MellonCond on the same
# attribute, and to mix MellonCond and MellonRequire.
# Note that this can create tricky situations, since the OR
# option has effect on a following MellonRequire directive.
#
# Default: none set
# MellonCond "mail" "@example\.net$" [OR,REG]
# MellonCond "mail" "@example\.com$" [OR,REG]
# MellonCond "uid" "superuser"
# MellonEndpointPath selects which directory mod_auth_mellon
# should assume contains the SAML 2.0 endpoints. Any request to
# this directory will be handled by mod_auth_mellon.

View File

@ -124,6 +124,30 @@ typedef enum {
am_decoder_feide,
} am_decoder_t;
typedef enum {
AM_COND_FLAG_NULL = 0x00, /* No flags */
AM_COND_FLAG_OR = 0x01, /* Or with next condition */
AM_COND_FLAG_NOT = 0x02, /* Negate this condition */
AM_COND_FLAG_REG = 0x04, /* Condition is regex */
AM_COND_FLAG_NC = 0x08, /* Case insensitive match */
AM_COND_FLAG_MAP = 0x10, /* Try to use attribute name from MellonSetEnv */
AM_COND_FLAG_IGN = 0x20, /* Condition is to be ignored */
AM_COND_FLAG_REQ = 0x40, /* Condition was configure using MellonRequire */
} am_cond_flag_t;
/* Not counting AM_COND_FLAG_NULL */
#define AM_COND_FLAG_COUNT 7
extern const char *am_cond_options[];
typedef struct {
const char *varname;
int flags;
const char *str;
ap_regex_t *regex;
const char *directive;
} am_cond_t;
typedef struct am_dir_cfg_rec {
/* enable_mellon is used to enable auth_mellon for a location.
*/
@ -136,7 +160,7 @@ typedef struct am_dir_cfg_rec {
const char *varname;
int secure;
apr_hash_t *require;
apr_array_header_t *cond;
apr_hash_t *envattr;
const char *userattr;
const char *idpattr;

View File

@ -422,6 +422,135 @@ static const char *am_set_setenv_slot(cmd_parms *cmd,
return NULL;
}
/* This function decodes MellonCond flags, such as [NOT,REG]
*
* Parameters:
* const char *arg Pointer to the flags string
*
* Returns:
* flags, or -1 on error
*/
const char *am_cond_options[] = {
"OR", /* AM_EXPIRE_FLAG_OR */
"NOT", /* AM_EXPIRE_FLAG_NOT */
"REG", /* AM_EXPIRE_FLAG_REG */
"NC", /* AM_EXPIRE_FLAG_NC */
"MAP", /* AM_EXPIRE_FLAG_MAP */
"IGN", /* AM_EXPIRE_FLAG_IGN */
"REQ", /* AM_EXPIRE_FLAG_REQ */
};
static int am_cond_flags(const char *arg)
{
int flags = AM_COND_FLAG_NULL;
/* Skip inital [ */
if (arg[0] == '[')
arg++;
else
return -1;
do {
apr_size_t i;
for (i = 0; i < AM_COND_FLAG_COUNT; i++) {
apr_size_t optlen = strlen(am_cond_options[i]);
if (strncmp(arg, am_cond_options[i], optlen) == 0) {
/* Make sure we have a separator next */
if (arg[optlen] && !strchr("]\t ,", (int)arg[optlen]))
return -1;
flags |= (1 << i);
arg += optlen;
break;
}
/* no match */
if (i == AM_COND_FLAG_COUNT)
return -1;
/* skip spaces, tabs and commas */
arg += strspn(arg, " \t,");
/* Garbage after ] is ignored */
if (*arg == ']')
return flags;
}
} while (*arg);
/* Missing trailing ] */
return -1;
}
/* This function handles the MellonCond configuration directive, which
* allows the user to restrict access based on attributes received from
* the IdP.
*
* Parameters:
* cmd_parms *cmd The command structure for the MellonCond
* configuration directive.
* void *struct_ptr Pointer to the current directory configuration.
* const char *attribute Pointer to the attribute name
* const char *value Pointer to the attribute value or regex
* const char *options Pointer to options
*
* Returns:
* NULL on success or an error string on failure.
*/
static const char *am_set_cond_slot(cmd_parms *cmd,
void *struct_ptr,
const char *attribute,
const char *value,
const char *options)
{
am_dir_cfg_rec *d = struct_ptr;
am_cond_t *element;
if (*attribute == '\0' || *value == '\0')
return apr_pstrcat(cmd->pool, cmd->cmd->name,
" takes two or three arguments", NULL);
element = (am_cond_t *)apr_array_push(d->cond);
element->varname = attribute;
element->flags = AM_COND_FLAG_NULL;
element->str = NULL;
element->regex = NULL;
element->directive = apr_pstrcat(cmd->pool, cmd->directive->directive,
" ", cmd->directive->args, NULL);
/* Handle optional flags */
if (*options != '\0') {
int flags;
flags = am_cond_flags(options);
if (flags == -1)
return apr_psprintf(cmd->pool, "%s - invalid flags %s",
cmd->cmd->name, options);
element->flags = flags;
}
if (element->flags & AM_COND_FLAG_REG) {
int regex_flags = AP_REG_EXTENDED|AP_REG_NOSUB;
if (element->flags & AM_COND_FLAG_NC)
regex_flags |= AP_REG_ICASE;
element->regex = ap_pregcomp(cmd->pool, value, regex_flags);
if (element->regex == NULL)
return apr_psprintf(cmd->pool, "%s - invalid regex %s",
cmd->cmd->name, value);
}
/*
* We keep the string also for regex, so that we can
* print it for debug purpose.
*/
element->str = value;
return NULL;
}
/* This function handles the MellonRequire configuration directive, which
* allows the user to restrict access based on attributes received from
@ -440,10 +569,11 @@ static const char *am_set_require_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg)
{
apr_array_header_t *r;
am_dir_cfg_rec *d = struct_ptr;
char *attribute, *value;
const char **element;
int i;
am_cond_t *element;
am_cond_t *first_element;
attribute = ap_getword_conf(cmd->pool, &arg);
value = ap_getword_conf(cmd->pool, &arg);
@ -453,20 +583,47 @@ static const char *am_set_require_slot(cmd_parms *cmd,
" takes at least two arguments", NULL);
}
/*
* MellonRequire overwrites previous conditions on this attribute
* We just tag the am_cond_t with the ignore flag, as it is
* easier (and probably faster) than to really remove it.
*/
for (i = 0; i < d->cond->nelts; i++) {
am_cond_t *ce = &((am_cond_t *)(d->cond->elts))[i];
if ((strcmp(ce->varname, attribute) == 0) &&
(ce->flags & AM_COND_FLAG_REQ))
ce->flags |= AM_COND_FLAG_IGN;
}
first_element = NULL;
do {
r = (apr_array_header_t *)apr_hash_get(d->require, attribute,
APR_HASH_KEY_STRING);
element = (am_cond_t *)apr_array_push(d->cond);
element->varname = attribute;
element->flags = AM_COND_FLAG_OR|AM_COND_FLAG_REQ;
element->str = value;
element->regex = NULL;
if (r == NULL) {
r = apr_array_make(cmd->pool, 2, sizeof(const char *));
apr_hash_set(d->require, attribute, APR_HASH_KEY_STRING, r);
/*
* When multiple values are given, we track the first one
* in order to retreive the directive
*/
if (first_element == NULL) {
element->directive = apr_pstrcat(cmd->pool,
cmd->directive->directive, " ",
cmd->directive->args, NULL);
first_element = element;
} else {
element->directive = first_element->directive;
}
element = (const char **)apr_array_push(r);
*element = value;
} while (*(value = ap_getword_conf(cmd->pool, &arg)) != '\0');
/*
* Remove OR flag on last element
*/
element->flags &= ~AM_COND_FLAG_OR;
return NULL;
}
@ -650,6 +807,15 @@ const command_rec auth_mellon_commands[] = {
" for the attribute. The syntax is:"
" MellonRequire <attribute> <value1> [value2....]."
),
AP_INIT_TAKE23(
"MellonCond",
am_set_cond_slot,
NULL,
OR_AUTHCFG,
"Attribute requirements for authorization. Allows you to restrict"
" access based on attributes received from the IdP. The syntax is:"
" MellonRequire <attribute> <value> [<options>]."
),
AP_INIT_TAKE1(
"MellonSessionLength",
ap_set_int_slot,
@ -795,7 +961,7 @@ void *auth_mellon_dir_config(apr_pool_t *p, char *d)
dir->varname = default_cookie_name;
dir->secure = default_secure_cookie;
dir->require = apr_hash_make(p);
dir->cond = apr_array_make(p, 0, sizeof(am_cond_t));
dir->envattr = apr_hash_make(p);
dir->userattr = default_user_attribute;
dir->idpattr = NULL;
@ -871,10 +1037,10 @@ void *auth_mellon_dir_merge(apr_pool_t *p, void *base, void *add)
base_cfg->secure);
new_cfg->require = apr_hash_copy(p,
(apr_hash_count(add_cfg->require) > 0) ?
add_cfg->require :
base_cfg->require);
new_cfg->cond = apr_array_copy(p,
(!apr_is_empty_array(add_cfg->cond)) ?
add_cfg->cond :
base_cfg->cond);
new_cfg->envattr = apr_hash_copy(p,
(apr_hash_count(add_cfg->envattr) > 0) ?

View File

@ -51,7 +51,7 @@ char *am_reconstruct_url(request_rec *r)
/* This function checks if the user has access according
* to the MellonRequire directives.
* to the MellonRequire and MellonCond directives.
*
* Parameters:
* request_rec *r The current request.
@ -63,51 +63,105 @@ char *am_reconstruct_url(request_rec *r)
int am_check_permissions(request_rec *r, am_cache_entry_t *session)
{
am_dir_cfg_rec *dir_cfg;
apr_hash_index_t *idx;
const char *key;
apr_array_header_t *rlist;
int i, j;
int rlist_ok;
const char **re;
int skip_or = 0;
dir_cfg = am_get_dir_cfg(r);
/* Iterate over all require-directives. */
for(idx = apr_hash_first(r->pool, dir_cfg->require);
idx != NULL;
idx = apr_hash_next(idx)) {
/* Iterate over all cond-directives */
for (i = 0; i < dir_cfg->cond->nelts; i++) {
am_cond_t *ce;
const char *value = NULL;
int match = 0;
/* Get current require directive. key will be the name
* of the attribute, and rlist is a list of all allowed values.
ce = &((am_cond_t *)(dir_cfg->cond->elts))[i];
/*
* Rule with ignore flog?
*/
apr_hash_this(idx, (const void **)&key, NULL, (void **)&rlist);
if (ce->flags & AM_COND_FLAG_IGN)
continue;
/* Reset status to 0 before search. */
rlist_ok = 0;
/*
* We matched a [OR] rule, skip the next rules
* until we have one without [OR].
*/
if (skip_or) {
if (!(ce->flags & AM_COND_FLAG_OR))
skip_or = 0;
re = (const char **)rlist->elts;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Skip %s, [OR] rule matched previously",
ce->directive);
continue;
}
/*
* look for a match on each value for this attribute,
* stop on first match.
*/
for (j = 0; (j < session->size) && !match; j++) {
const char *varname = NULL;
/* rlist is an array of all the valid values for this attribute. */
for(i = 0; i < rlist->nelts && !rlist_ok; i++) {
/*
* if MAP flag is set, check for remapped
* attribute name with mellonSetEnv
*/
if (ce->flags & AM_COND_FLAG_MAP)
varname = apr_hash_get(dir_cfg->envattr,
session->env[j].varname,
APR_HASH_KEY_STRING);
/* Search for a matching attribute in the session data. */
for(j = 0; j < session->size && !rlist_ok; j++) {
if(strcmp(session->env[j].varname, key) == 0 &&
strcmp(session->env[j].value, re[i]) == 0) {
/* We found a attribute with the correct name
* and value.
*/
rlist_ok = 1;
}
/*
* Otherwise or if not found, use the attribute name
* sent by the IdP.
*/
if (varname == NULL)
varname = session->env[j].varname;
if (strcmp(varname, ce->varname) != 0)
continue;
value = session->env[j].value;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"Evaluate %s vs \"%s\"",
ce->directive, value);
if (value == NULL) {
match = 0; /* can not happen */
} else if (ce->flags & AM_COND_FLAG_REG) {
match = !ap_regexec(ce->regex, value, 0, NULL, 0);
} else if (ce->flags & AM_COND_FLAG_NC) {
match = !strcasecmp(ce->str, value);
} else {
match = !strcmp(ce->str, value);
}
}
if(!rlist_ok) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Client failed to match required attribute \"%s\".",
key);
if (ce->flags & AM_COND_FLAG_NOT)
match = !match;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"%s: %smatch", ce->directive,
(match == 0) ? "no ": "");
/*
* If no match, we stop here, except if it is an [OR] condition
*/
if (!match & !(ce->flags & AM_COND_FLAG_OR)) {
ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
"Client failed to match %s",
ce->directive);
return HTTP_FORBIDDEN;
}
/*
* Match on [OR] condition means we skip until a rule
* without [OR],
*/
if (match && (ce->flags & AM_COND_FLAG_OR))
skip_or = 1;
}
return OK;