]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
rlm_json: add json_encode xlat; converts attributes to JSON document
authorMatthew Newton <matthew-git@newtoncomputing.co.uk>
Thu, 16 Jan 2020 17:34:37 +0000 (17:34 +0000)
committerMatthew Newton <matthew-git@newtoncomputing.co.uk>
Fri, 21 Feb 2020 19:10:06 +0000 (19:10 +0000)
src/modules/rlm_json/rlm_json.c

index 35bf8e3f2bc20c04aad138ae45ac231e702828c1..3f34df93e9314e6c09b6d27b0d99b1c2f99b8d8b 100644 (file)
  * @brief Parses JSON responses
  *
  * @author Arran Cudbard-Bell
+ * @author Matthew Newton
  *
  * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- * @copyright 2015 Network RADIUS SARL (legal@networkradius.com)
+ * @copyright 2015,2020 Network RADIUS SARL (legal@networkradius.com)
  * @copyright 2015 The FreeRADIUS Server Project
  */
 RCSID("$Id$")
@@ -39,6 +40,35 @@ RCSID("$Id$")
 #  error "rlm_json should not be built unless json-c is available"
 #endif
 
+/** rlm_json module instance
+ *
+ */
+typedef struct {
+       char const              *name;
+       fr_json_format_t        *format;
+} rlm_json_t;
+
+
+static CONF_PARSER const module_config[] = {
+       { FR_CONF_OFFSET("encode", FR_TYPE_SUBSECTION, rlm_json_t, format),
+         .subcs_size = sizeof(fr_json_format_t), .subcs_type = "fr_json_format_t",
+         .subcs = (void const *) fr_json_format_config },
+
+       CONF_PARSER_TERMINATOR
+};
+
+
+/** Boilerplate to copy the pointer to the main module config into the xlat instance data
+ *
+ */
+static int json_xlat_instantiate(void *xlat_inst, UNUSED xlat_exp_t const *exp, void *uctx)
+{
+       *((rlm_json_t **)xlat_inst) = talloc_get_type_abort(uctx, rlm_json_t);
+
+       return 0;
+}
+
+
 /** Forms a linked list of jpath head node pointers (a list of jpaths)
  */
 typedef struct rlm_json_jpath_cache rlm_json_jpath_cache_t;
@@ -105,6 +135,144 @@ static ssize_t jpath_validate_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t ou
        return ret;
 }
 
+/** Convert given attributes to a JSON document
+ *
+ * Usage is `%{json_encode:attr tmpl list}`
+ *
+ * @param ctx talloc context
+ * @param out where to write the output
+ * @param request the current request
+ * @param xlat_inst xlat instance data
+ * @param xlat_thread_inst unused
+ * @param in list of value boxes as input
+ * @return XLAT_ACTION_DONE or XLAT_ACTION_FAIL
+ */
+static xlat_action_t json_encode_xlat(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request,
+                                     void const *xlat_inst, UNUSED void *xlat_thread_inst,
+                                     fr_value_box_t **in)
+{
+       rlm_json_t const *inst = talloc_get_type_abort_const(*((void const * const *)xlat_inst),
+                                                            rlm_json_t);
+       fr_json_format_t const *format = inst->format;
+
+       ssize_t slen;
+       vp_tmpl_t *vpt = NULL;
+       fr_cursor_t cursor;
+       fr_cursor_t filter;
+       VALUE_PAIR *json_vps = NULL;
+       VALUE_PAIR *vps = NULL;
+       bool negate;
+       char *json_str = NULL;
+       char const *start;
+       char const *p;
+       char const *end;
+       fr_value_box_t *vb;
+
+       if (!in) {
+               REDEBUG("Missing attribute(s)");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) {
+               RPEDEBUG("Failed concatenating input");
+               return XLAT_ACTION_FAIL;
+       }
+
+       start = p = (*in)->vb_strvalue;
+       end = p + (*in)->vb_length;
+
+       fr_bskip_whitespace(p, end);
+
+       /*
+        * Iterate through the list of attribute templates in the xlat. For each
+        * one we either add it to the list of attributes for the JSON document
+        * or, if prefixed with '!', remove from the JSON list.
+        */
+       while (p < end) {
+               negate = false;
+
+               /* Check if we should be removing attributes */
+               if (*p == '!') {
+                       negate = true;
+                       p++;
+               }
+
+               /* Decode next attr template */
+               slen = tmpl_afrom_attr_substr(ctx, NULL, &vpt, p, -1,
+                       &(vp_tmpl_rules_t){ .dict_def = request->dict });
+
+               if (slen <= 0) {
+                       REMARKER(start, (p-start) + (-slen), "Invalid input");
+                       error:
+                               fr_pair_list_free(&json_vps);
+                               talloc_free(vpt);
+                               return XLAT_ACTION_FAIL;
+               }
+
+               /*
+                * Get attributes from the template.
+                * Missing attribute isn't an error (so -1, not 0).
+                */
+               if (tmpl_copy_vps(ctx, &vps, request, vpt) < -1) {
+                       RPEDEBUG("Error copying attributes");
+                       goto error;
+               }
+
+               if (negate) {
+                       /* Remove all template attributes from JSON list */
+                       for (VALUE_PAIR *vp = fr_cursor_init(&filter, &vps);
+                            vp;
+                            vp = fr_cursor_next(&filter)) {
+
+                               VALUE_PAIR *vpm = fr_cursor_init(&cursor, &json_vps);
+                               while (vpm) {
+                                       if (vp->da == vpm->da) {
+                                               talloc_free(fr_cursor_remove(&cursor));
+                                               vpm = fr_cursor_current(&cursor);
+                                               continue;
+                                       }
+                                       vpm = fr_cursor_next(&cursor);
+                               }
+                       }
+
+                       fr_pair_list_free(&vps);
+               } else {
+                       /* Add template VPs to JSON list */
+                       fr_pair_add(&json_vps, vps);
+               }
+
+               TALLOC_FREE(vpt);
+
+               /* Jump forward to next attr */
+               p += slen;
+               fr_bskip_whitespace(p, end);
+       }
+
+       /*
+        * Given the list of attributes we now have in json_vps,
+        * convert them into a JSON document and append it to the
+        * return cursor.
+        */
+       MEM(vb = fr_value_box_alloc_null(ctx));
+
+       json_str = fr_json_afrom_pair_list(vb, json_vps, format);
+       if (!json_str) {
+               REDEBUG("Failed to generate JSON string");
+               goto error;
+       }
+
+       if (unlikely(fr_value_box_bstrsteal(vb, vb, NULL, json_str, false) < 0)) {
+               REDEBUG("Failed to allocate JSON string");
+               goto error;
+       }
+
+       fr_cursor_append(out, vb);
+       fr_pair_list_free(&json_vps);
+
+       return XLAT_ACTION_DONE;
+}
+
+
 /** Pre-parse and validate literal jpath expressions for maps
  *
  * @param[in] cs       #CONF_SECTION that defined the map instance.
@@ -345,11 +513,36 @@ finish:
        return rcode;
 }
 
-static int mod_bootstrap(void *instance, UNUSED CONF_SECTION *conf)
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
 {
+       rlm_json_t              *inst = talloc_get_type_abort(instance, rlm_json_t);
+       xlat_t const            *xlat;
+       char                    *name;
+       fr_json_format_t        *format = inst->format;
+
+       inst->name = cf_section_name2(conf);
+       if (!inst->name) inst->name = cf_section_name1(conf);
+
        xlat_register(instance, "jsonquote", jsonquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
        xlat_register(instance, "jpathvalidate", jpath_validate_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN, true);
 
+       name = talloc_asprintf(inst, "%s_encode", inst->name);
+       xlat = xlat_async_register(instance, name, json_encode_xlat);
+       xlat_async_instantiate_set(xlat, json_xlat_instantiate,
+                                  rlm_json_t *, NULL, inst);
+       talloc_free(name);
+
+       /*
+        *      Check the output format type and warn on unused
+        *      format options
+        */
+       format->output_mode = fr_table_value_by_str(fr_json_format_table, format->output_mode_str, JSON_MODE_UNSET);
+       if (format->output_mode == JSON_MODE_UNSET) {
+               cf_log_err(conf, "output_mode value \"%s\" is invalid", format->output_mode_str);
+               return -1;
+       }
+       fr_json_format_verify(format, true);
+
        if (map_proc_register(instance, "json", mod_map_proc,
                              mod_map_proc_instantiate, sizeof(rlm_json_jpath_cache_t)) < 0) return -1;
        return 0;
@@ -377,5 +570,7 @@ module_t rlm_json = {
        .name           = "json",
        .type           = RLM_TYPE_THREAD_SAFE,
        .onload         = mod_load,
+       .config         = module_config,
+       .inst_size      = sizeof(rlm_json_t),
        .bootstrap      = mod_bootstrap,
 };