summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormanu@netbsd.org <manu@netbsd.org@a716ebb1-153a-0410-b759-cfb97c6a1b53>2011-03-17 05:20:40 +0000
committermanu@netbsd.org <manu@netbsd.org@a716ebb1-153a-0410-b759-cfb97c6a1b53>2011-03-17 05:20:40 +0000
commit000b791af8867c970bf17fb689d5d9707f54adfc (patch)
tree6d5d90bed0585254e467eb0b4478a8f120f24d89
parent99795c36c245a7d7128749cc8fc6750ffb657d84 (diff)
downloadmod_auth_mellon-000b791af8867c970bf17fb689d5d9707f54adfc.tar.gz
mod_auth_mellon-000b791af8867c970bf17fb689d5d9707f54adfc.tar.xz
mod_auth_mellon-000b791af8867c970bf17fb689d5d9707f54adfc.zip
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@114 a716ebb1-153a-0410-b759-cfb97c6a1b53
-rw-r--r--NEWS3
-rw-r--r--README45
-rw-r--r--auth_mellon.h26
-rw-r--r--auth_mellon_config.c198
-rw-r--r--auth_mellon_util.c120
5 files changed, 335 insertions, 57 deletions
diff --git a/NEWS b/NEWS
index b633ac1..4d17f8d 100644
--- a/NEWS
+++ b/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
---------------------------------------------------------------------------
diff --git a/README b/README
index e77370c..96bfe01 100644
--- a/README
+++ b/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.
diff --git a/auth_mellon.h b/auth_mellon.h
index 247b6a6..8219853 100644
--- a/auth_mellon.h
+++ b/auth_mellon.h
@@ -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;
diff --git a/auth_mellon_config.c b/auth_mellon_config.c
index eba1fa4..e2323d3 100644
--- a/auth_mellon_config.c
+++ b/auth_mellon_config.c
@@ -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);
-
- if (r == NULL) {
- r = apr_array_make(cmd->pool, 2, sizeof(const char *));
- apr_hash_set(d->require, attribute, APR_HASH_KEY_STRING, r);
+ 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;
+
+ /*
+ * 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) ?
diff --git a/auth_mellon_util.c b/auth_mellon_util.c
index 8f5bf9d..ddad6a7 100644
--- a/auth_mellon_util.c
+++ b/auth_mellon_util.c
@@ -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.
- */
- apr_hash_this(idx, (const void **)&key, NULL, (void **)&rlist);
-
- /* Reset status to 0 before search. */
- rlist_ok = 0;
+ ce = &((am_cond_t *)(dir_cfg->cond->elts))[i];
- re = (const char **)rlist->elts;
+ /*
+ * Rule with ignore flog?
+ */
+ if (ce->flags & AM_COND_FLAG_IGN)
+ continue;
- /* rlist is an array of all the valid values for this attribute. */
- for(i = 0; i < rlist->nelts && !rlist_ok; i++) {
+ /*
+ * 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;
- /* 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;
- }
+ 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;
+
+ /*
+ * 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);
+
+ /*
+ * 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;