diff options
author | manu@netbsd.org <manu@netbsd.org@a716ebb1-153a-0410-b759-cfb97c6a1b53> | 2011-03-17 05:20:40 +0000 |
---|---|---|
committer | manu@netbsd.org <manu@netbsd.org@a716ebb1-153a-0410-b759-cfb97c6a1b53> | 2011-03-17 05:20:40 +0000 |
commit | 000b791af8867c970bf17fb689d5d9707f54adfc (patch) | |
tree | 6d5d90bed0585254e467eb0b4478a8f120f24d89 | |
parent | 99795c36c245a7d7128749cc8fc6750ffb657d84 (diff) | |
download | mod_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-- | NEWS | 3 | ||||
-rw-r--r-- | README | 45 | ||||
-rw-r--r-- | auth_mellon.h | 26 | ||||
-rw-r--r-- | auth_mellon_config.c | 198 | ||||
-rw-r--r-- | auth_mellon_util.c | 120 |
5 files changed, 335 insertions, 57 deletions
@@ -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 --------------------------------------------------------------------------- @@ -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; |