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:
parent
7cb7b968e3
commit
4a774de00c
3
NEWS
3
NEWS
|
@ -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
45
README
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) ?
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue