From: Terry Burton Date: Mon, 2 Jun 2025 14:19:48 +0000 (+0100) Subject: rlm_json: New dates_at_integer CI to render seconds since Unix epoch X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dce391b4e88a7db3ced564fad126ed67c96eb9d8;p=thirdparty%2Ffreeradius-server.git rlm_json: New dates_at_integer CI to render seconds since Unix epoch --- diff --git a/raddb/mods-available/json b/raddb/mods-available/json index 88f17c030b..bdf74d4240 100644 --- a/raddb/mods-available/json +++ b/raddb/mods-available/json @@ -79,6 +79,17 @@ json { # # enum_as_integer = no + # + # dates_as_integer:: output dates as seconds since the + # epoch + # + # Where an attribute is a date, the textual + # representation of the date will normally be output. + # Enable this option to force the date to be rendered + # as seconds since the epoch instead. + # +# dates_as_integer = no + # # always_string:: force all values to be strings # diff --git a/src/modules/rlm_json/json.c b/src/modules/rlm_json/json.c index 83bcba5842..e8647f49ba 100644 --- a/src/modules/rlm_json/json.c +++ b/src/modules/rlm_json/json.c @@ -63,9 +63,10 @@ void json_object_put_assert(json_object *obj) * @param[in] vp VALUE_PAIR to convert. * @param[in] always_string create all values as strings * @param[in] enum_as_int output enum attribute values as integers not strings + * @param[in] dates_as_int output date values as seconds since the epoch * @return Newly allocated JSON object, or NULL on error */ -json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, bool always_string, bool enum_as_int) +json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, bool always_string, bool enum_as_int, bool dates_as_int) { char buf[2048]; ssize_t len; @@ -107,6 +108,11 @@ json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, } } + /* + * We handle dates as epoch seconds here and dates as strings later. + */ + if (vp->da->type == PW_TYPE_DATE && dates_as_int) + return json_object_new_int(vp->vp_date); /* * If always_string is set then we print everything to a string and @@ -229,7 +235,7 @@ static int json_afrom_value_pair(TALLOC_CTX *ctx, json_object **out, fr_assert(vp); fr_assert(inst); - MEM(obj = json_object_from_attr_value(ctx, vp, inst->always_string, inst->enum_as_int)); + MEM(obj = json_object_from_attr_value(ctx, vp, inst->always_string, inst->enum_as_int, inst->dates_as_int)); *out = obj; return is_enum; @@ -302,6 +308,10 @@ bool fr_json_format_verify(rlm_json_t const *inst, bool verbose) if (verbose) WARN("'enum_as_int' not valid in output_mode 'array_of_names' and will be ignored"); ret = false; } + if (inst->dates_as_int) { + if (verbose) WARN("'dates_as_int' not valid in output_mode 'array_of_names' and will be ignored"); + ret = false; + } if (inst->always_string) { if (verbose) WARN("'always_string' not valid in output_mode 'array_of_names' and will be ignored"); ret = false; diff --git a/src/modules/rlm_json/json.h b/src/modules/rlm_json/json.h index d05ae120af..132db6f2fa 100644 --- a/src/modules/rlm_json/json.h +++ b/src/modules/rlm_json/json.h @@ -78,6 +78,7 @@ typedef struct { char const *attr_prefix; //!< Prefix to add to all attribute names bool value_as_array; //!< Use JSON array for multiple attribute values. bool enum_as_int; //!< Output enums as value, not their string representation. + bool dates_as_int; //!< Output dates as epoch seconds, not their string representation. bool always_string; //!< Output all data types as strings. @@ -90,7 +91,7 @@ typedef struct { } rlm_json_t; -json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, bool always_string, bool enum_as_int); +json_object *json_object_from_attr_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, bool always_string, bool enum_as_int, bool dates_as_int); void fr_json_version_print(void); char *fr_json_afrom_pair_list(TALLOC_CTX *ctx, VALUE_PAIR *vps, rlm_json_t const *format); diff --git a/src/modules/rlm_json/rlm_json.c b/src/modules/rlm_json/rlm_json.c index a2476e8f1f..3a785f9fd5 100644 --- a/src/modules/rlm_json/rlm_json.c +++ b/src/modules/rlm_json/rlm_json.c @@ -48,6 +48,7 @@ static CONF_PARSER const json_format_attr_config[] = { static CONF_PARSER const json_format_value_config[] = { { "single_value_as_array", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, value_as_array), "no" }, { "enum_as_integer", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, enum_as_int), "no" }, + { "dates_as_integer", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, dates_as_int), "no" }, { "always_string", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_json_t, always_string), "no" }, CONF_PARSER_TERMINATOR diff --git a/src/tests/modules/json/encode.attrs b/src/tests/modules/json/encode.attrs index ea8d653ad6..98a47576d9 100644 --- a/src/tests/modules/json/encode.attrs +++ b/src/tests/modules/json/encode.attrs @@ -6,6 +6,7 @@ Filter-Id = "f1" Filter-Id += "f2" NAS-Port = 999 Service-Type = Login-User +Event-Timestamp = 1234 # # Expected answer diff --git a/src/tests/modules/json/encode.unlang b/src/tests/modules/json/encode.unlang index 6c7a1fe0b8..dfd6206b41 100644 --- a/src/tests/modules/json/encode.unlang +++ b/src/tests/modules/json/encode.unlang @@ -18,7 +18,7 @@ update control { } -if (&control:Tmp-String-1 != '{"User-Name":{"type":"string","value":"john"},"Filter-Id":{"type":"string","value":["f1","f2"]},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"}}') { +if (&control:Tmp-String-1 != '{"User-Name":{"type":"string","value":"john"},"Filter-Id":{"type":"string","value":["f1","f2"]},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"},"Event-Timestamp":{"type":"date","value":"Jan 1 1970 00:20:34 UTC"}}') { test_fail } @@ -35,7 +35,7 @@ if (&control:Tmp-String-1 != &control:Tmp-String-7 || \ test_fail } -if (&control:Tmp-String-5 != '{"User-Name":{"type":"string","value":"john"},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"}}') { +if (&control:Tmp-String-5 != '{"User-Name":{"type":"string","value":"john"},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"},"Event-Timestamp":{"type":"date","value":"Jan 1 1970 00:20:34 UTC"}}') { test_fail } @@ -65,11 +65,11 @@ update control { &Tmp-String-2 := "%{json_object_ex_encode:&request:[*]}" } -if (&control:Tmp-String-1 != '{"User-Name":{"type":"string","value":"john"},"Filter-Id":{"type":"string","value":["f1","f2"]},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"}}') { +if (&control:Tmp-String-1 != '{"User-Name":{"type":"string","value":"john"},"Filter-Id":{"type":"string","value":["f1","f2"]},"NAS-Port":{"type":"integer","value":999},"Service-Type":{"type":"integer","value":"Login-User"},"Event-Timestamp":{"type":"date","value":"Jan 1 1970 00:20:34 UTC"}}') { test_fail } -if (&control:Tmp-String-2 != '{"pf:User-Name":{"type":"string","value":["john"]},"pf:Filter-Id":{"type":"string","value":["f1","f2"]},"pf:NAS-Port":{"type":"integer","value":["999"]},"pf:Service-Type":{"type":"integer","value":["1"]}}') { +if (&control:Tmp-String-2 != '{"pf:User-Name":{"type":"string","value":["john"]},"pf:Filter-Id":{"type":"string","value":["f1","f2"]},"pf:NAS-Port":{"type":"integer","value":["999"]},"pf:Service-Type":{"type":"integer","value":["1"]},"pf:Event-Timestamp":{"type":"date","value":[1234]}}') { test_fail } @@ -97,11 +97,11 @@ update control { &Tmp-String-2 := "%{json_object_simple_ex_encode:&request:[*]}" } -if (&control:Tmp-String-1 != '{"User-Name":"john","Filter-Id":["f1","f2"],"NAS-Port":999,"Service-Type":"Login-User"}') { +if (&control:Tmp-String-1 != '{"User-Name":"john","Filter-Id":["f1","f2"],"NAS-Port":999,"Service-Type":"Login-User","Event-Timestamp":"Jan 1 1970 00:20:34 UTC"}') { test_fail } -if (&control:Tmp-String-2 != '{"pf:User-Name":["john"],"pf:Filter-Id":["f1","f2"],"pf:NAS-Port":["999"],"pf:Service-Type":["1"]}') { +if (&control:Tmp-String-2 != '{"pf:User-Name":["john"],"pf:Filter-Id":["f1","f2"],"pf:NAS-Port":["999"],"pf:Service-Type":["1"],"pf:Event-Timestamp":[1234]}') { test_fail } @@ -129,11 +129,11 @@ update control { &Tmp-String-2 := "%{json_array_ex_encode:&request:[*]}" } -if (&control:Tmp-String-1 != '[{"name":"User-Name","type":"string","value":"john"},{"name":"Filter-Id","type":"string","value":"f1"},{"name":"Filter-Id","type":"string","value":"f2"},{"name":"NAS-Port","type":"integer","value":999},{"name":"Service-Type","type":"integer","value":"Login-User"}]') { +if (&control:Tmp-String-1 != '[{"name":"User-Name","type":"string","value":"john"},{"name":"Filter-Id","type":"string","value":"f1"},{"name":"Filter-Id","type":"string","value":"f2"},{"name":"NAS-Port","type":"integer","value":999},{"name":"Service-Type","type":"integer","value":"Login-User"},{"name":"Event-Timestamp","type":"date","value":"Jan 1 1970 00:20:34 UTC"}]') { test_fail } -if (&control:Tmp-String-2 != '[{"name":"pf:User-Name","type":"string","value":["john"]},{"name":"pf:Filter-Id","type":"string","value":["f1","f2"]},{"name":"pf:NAS-Port","type":"integer","value":["999"]},{"name":"pf:Service-Type","type":"integer","value":["1"]}]') { +if (&control:Tmp-String-2 != '[{"name":"pf:User-Name","type":"string","value":["john"]},{"name":"pf:Filter-Id","type":"string","value":["f1","f2"]},{"name":"pf:NAS-Port","type":"integer","value":["999"]},{"name":"pf:Service-Type","type":"integer","value":["1"]},{"name":"pf:Event-Timestamp","type":"date","value":[1234]}]') { test_fail } @@ -161,11 +161,11 @@ update control { &Tmp-String-2 := "%{json_array_names_ex_encode:&request:[*]}" } -if (&control:Tmp-String-1 != '["User-Name","Filter-Id","Filter-Id","NAS-Port","Service-Type"]') { +if (&control:Tmp-String-1 != '["User-Name","Filter-Id","Filter-Id","NAS-Port","Service-Type","Event-Timestamp"]') { test_fail } -if (&control:Tmp-String-2 != '["pf:User-Name","pf:Filter-Id","pf:Filter-Id","pf:NAS-Port","pf:Service-Type"]') { +if (&control:Tmp-String-2 != '["pf:User-Name","pf:Filter-Id","pf:Filter-Id","pf:NAS-Port","pf:Service-Type","pf:Event-Timestamp"]') { test_fail } @@ -193,11 +193,11 @@ update control { &Tmp-String-2 := "%{json_array_values_ex_encode:&request:[*]}" } -if (&control:Tmp-String-1 != '["john","f1","f2",999,"Login-User"]') { +if (&control:Tmp-String-1 != '["john","f1","f2",999,"Login-User","Jan 1 1970 00:20:34 UTC"]') { test_fail } -if (&control:Tmp-String-2 != '["john","f1","f2","999","1"]') { +if (&control:Tmp-String-2 != '["john","f1","f2","999","1",1234]') { test_fail } diff --git a/src/tests/modules/json/module.conf b/src/tests/modules/json/module.conf index 04d1b1d460..5893d6df3f 100644 --- a/src/tests/modules/json/module.conf +++ b/src/tests/modules/json/module.conf @@ -19,6 +19,7 @@ json json_object_no { value { single_value_as_array = no enum_as_integer = no + dates_as_integer = no always_string = no } } @@ -36,6 +37,7 @@ json json_object_ex { value { single_value_as_array = yes enum_as_integer = yes + dates_as_integer = yes always_string = yes } } @@ -63,6 +65,7 @@ json json_object_simple_ex { value { single_value_as_array = yes enum_as_integer = yes + dates_as_integer = yes always_string = yes } } @@ -90,6 +93,7 @@ json json_array_ex { value { single_value_as_array = yes enum_as_integer = yes + dates_as_integer = yes always_string = yes } } @@ -117,6 +121,7 @@ json json_array_names_ex { value { single_value_as_array = yes # not valid enum_as_integer = yes # not valid + dates_as_integer = yes # not valid always_string = yes # not valid } } @@ -144,6 +149,7 @@ json json_array_values_ex { value { single_value_as_array = yes # not valid enum_as_integer = yes + dates_as_integer = yes always_string = yes } }