From: Matthew Newton Date: Thu, 16 Jan 2020 17:34:37 +0000 (+0000) Subject: rlm_json: add json_encode xlat; converts attributes to JSON document X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=513baa81f4e95bed0e75c27342d60ffce8e6d9fa;p=thirdparty%2Ffreeradius-server.git rlm_json: add json_encode xlat; converts attributes to JSON document --- diff --git a/src/modules/rlm_json/rlm_json.c b/src/modules/rlm_json/rlm_json.c index 35bf8e3f2bc..3f34df93e93 100644 --- a/src/modules/rlm_json/rlm_json.c +++ b/src/modules/rlm_json/rlm_json.c @@ -20,9 +20,10 @@ * @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, };