]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
rlm_json: New dates_at_integer CI to render seconds since Unix epoch
authorTerry Burton <tez@terryburton.co.uk>
Mon, 2 Jun 2025 14:19:48 +0000 (15:19 +0100)
committerMatthew Newton <matthew-git@newtoncomputing.co.uk>
Wed, 4 Jun 2025 13:32:33 +0000 (14:32 +0100)
raddb/mods-available/json
src/modules/rlm_json/json.c
src/modules/rlm_json/json.h
src/modules/rlm_json/rlm_json.c
src/tests/modules/json/encode.attrs
src/tests/modules/json/encode.unlang
src/tests/modules/json/module.conf

index 88f17c030bfed7e11f2144a6f74874e662d3e38c..bdf74d4240c78e73ea5acb405d73890f489f01a8 100644 (file)
@@ -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
                        #
index 83bcba5842f2918fab14f53abca319bd05d25e30..e8647f49bac197e349cee6460ecb3038ddcb27e1 100644 (file)
@@ -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;
index d05ae120af65d637a8205d5da71df5955e8e873a..132db6f2fa50364ebb04c0f4122f1202a84464c6 100644 (file)
@@ -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);
index a2476e8f1fa858177a2952384dc19dd9d58081b6..3a785f9fd5b53a615d5db97d767825fa19fb7e16 100644 (file)
@@ -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
index ea8d653ad6e4035e64c0a4129348dbcedf074a48..98a47576d9f6f854e7e541650679d3d612748d92 100644 (file)
@@ -6,6 +6,7 @@ Filter-Id = "f1"
 Filter-Id += "f2"
 NAS-Port = 999
 Service-Type = Login-User
+Event-Timestamp = 1234
 
 #
 #  Expected answer
index 6c7a1fe0b88b66e43069e42b7ada0bfa351615ec..dfd6206b41c48b5e162752e88bca2eb5eb40294f 100644 (file)
@@ -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
 }
 
index 04d1b1d460226f6543a7ec66171dd7ef0f192b51..5893d6df3ff1bb42cdbc646e969050c45cad463d 100644 (file)
@@ -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
                }
        }