$NetBSD: patch-ac,v 1.1 2011/04/04 08:46:42 manu Exp $

Add MellonCond directive (pulled up from upstream)

Index: auth_mellon_util.c
===================================================================
--- auth_mellon_util.c	(revision 113)
+++ auth_mellon_util.c	(working copy)
@@ -51,7 +51,7 @@
 
 
 /* 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 @@
 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;
Index: README
===================================================================
--- README	(revision 113)
+++ README	(working copy)
@@ -152,9 +152,9 @@
         #   "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 @@
         # 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.
Index: auth_mellon.h
===================================================================
--- auth_mellon.h	(revision 113)
+++ auth_mellon.h	(working copy)
@@ -124,6 +124,30 @@
     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 @@
 
     const char *varname;
     int secure;
-    apr_hash_t *require;
+    apr_array_header_t *cond;
     apr_hash_t *envattr;
     const char *userattr;
     const char *idpattr;
Index: auth_mellon_config.c
===================================================================
--- auth_mellon_config.c	(revision 113)
+++ auth_mellon_config.c	(working copy)
@@ -422,7 +422,136 @@
     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
  * the IdP.
@@ -440,10 +569,11 @@
                                        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 @@
                            " 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 @@
         " 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 @@
 
     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 @@
                         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) ?
