Blob Blame History Raw
From 3e11b3c67bc31662810f72571311d7d7580e3f0a Mon Sep 17 00:00:00 2001
From: Jakub Filak <jfilak@redhat.com>
Date: Mon, 18 Aug 2014 20:39:34 +0200
Subject: [SATYR PATCH 6/6] Add authentication data support in uReport

Support authentication data in uReport

We need this for 'machine id' element which should be present only if
users wishes to it.

The authentication entries are string based, key value pairs (JSON
string elements).

The entries are enclosed in an envelope object called 'auth'.

Example:

"auth" : {   "machineid" : "deadbeaf"
         }

Fixes #171

Signed-off-by: Jakub Filak <jfilak@redhat.com>
Signed-off-by: Martin Milata <mmilata@redhat.com>

mmilata: s/custom/auth/g, python bindings

tests: add a test for uReport auth data

Related to #171

Signed-off-by: Jakub Filak <jfilak@redhat.com>
Signed-off-by: Martin Milata <mmilata@redhat.com>

mmilata: python test

ureport: fix indentation of 'auth' key in JSON

Fixes a bug in commit 189f7adbba9c38bd434c30e7d0c07b546c373312

Related to #171

Signed-off-by: Jakub Filak <jfilak@redhat.com>
---
 include/report.h                |  17 ++++++
 lib/internal_utils.h            |  12 +++++
 lib/json.c                      |  26 ++++++++++
 lib/report.c                    |  82 +++++++++++++++++++++++++++++
 python/py_report.c              |  30 +++++++++++
 python/py_report.h              |   2 +
 tests/json_files/ureport-1-auth | 112 ++++++++++++++++++++++++++++++++++++++++
 tests/python/report.py          |   6 +++
 tests/report.at                 |  81 +++++++++++++++++++++++++++++
 9 files changed, 368 insertions(+)
 create mode 100644 tests/json_files/ureport-1-auth

diff --git a/include/report.h b/include/report.h
index 424b730..025d8f8 100644
--- a/include/report.h
+++ b/include/report.h
@@ -31,6 +31,13 @@ extern "C" {
 struct sr_json_value;
 struct sr_stacktrace;
 
+struct sr_report_custom_entry
+{
+    char *key;
+    char *value;
+    struct sr_report_custom_entry *next;
+};
+
 struct sr_report
 {
     uint32_t report_version;
@@ -49,6 +56,8 @@ struct sr_report
     struct sr_rpm_package *rpm_packages;
 
     struct sr_stacktrace *stacktrace;
+
+    struct sr_report_custom_entry *auth_entries;
 };
 
 struct sr_report *
@@ -60,6 +69,14 @@ sr_report_init(struct sr_report *report);
 void
 sr_report_free(struct sr_report *report);
 
+
+/* @brief Adds a new entry to 'auth' object
+ *
+ * The implementation is LIFO. The resulting list is in reversed.
+ */
+void
+sr_report_add_auth(struct sr_report *report, const char *key, const char *value);
+
 char *
 sr_report_to_json(struct sr_report *report);
 
diff --git a/lib/internal_utils.h b/lib/internal_utils.h
index 20f65de..3a10520 100644
--- a/lib/internal_utils.h
+++ b/lib/internal_utils.h
@@ -130,4 +130,16 @@ json_read_bool(struct sr_json_value *object, const char *key_name, bool *dest,
         abort();                                                                      \
     }
 
+/* returns the number of object's children */
+unsigned
+json_object_children_count(struct sr_json_value *object);
+
+/* returns the child_noth child and passes its name in child_name arg */
+struct sr_json_value *
+json_object_get_child(struct sr_json_value *object, unsigned child_no, const char **child_name);
+
+/* returns string's value */
+const char *
+json_string_get_value(struct sr_json_value *object);
+
 #endif
diff --git a/lib/json.c b/lib/json.c
index 35b7e6b..d777973 100644
--- a/lib/json.c
+++ b/lib/json.c
@@ -787,6 +787,32 @@ json_element(struct sr_json_value *object, const char *key_name)
     return NULL;
 }
 
+unsigned
+json_object_children_count(struct sr_json_value *object)
+{
+    assert(object->type == SR_JSON_OBJECT);
+
+    return object->u.object.length;
+}
+
+struct sr_json_value *
+json_object_get_child(struct sr_json_value *object, unsigned child_no, const char **child_name)
+{
+    assert(object->type == SR_JSON_OBJECT);
+    assert(child_no < object->u.object.length);
+
+    *child_name = object->u.object.values[child_no].name;
+    return object->u.object.values[child_no].value;
+}
+
+const char *
+json_string_get_value(struct sr_json_value *object)
+{
+    assert(object->type == SR_JSON_STRING);
+
+    return object->u.string.ptr;
+}
+
 #define DEFINE_JSON_READ(name, c_type, json_type, json_member, conversion)                       \
     bool                                                                                         \
     name(struct sr_json_value *object, const char *key_name, c_type *dest, char **error_message) \
diff --git a/lib/report.c b/lib/report.c
index a6ecae4..411df64 100644
--- a/lib/report.c
+++ b/lib/report.c
@@ -66,6 +66,7 @@ sr_report_init(struct sr_report *report)
     report->component_name = NULL;
     report->rpm_packages = NULL;
     report->stacktrace = NULL;
+    report->auth_entries = NULL;
 }
 
 void
@@ -75,9 +76,36 @@ sr_report_free(struct sr_report *report)
     sr_operating_system_free(report->operating_system);
     sr_rpm_package_free(report->rpm_packages, true);
     sr_stacktrace_free(report->stacktrace);
+
+    struct sr_report_custom_entry *iter = report->auth_entries;
+    while (iter)
+    {
+        struct sr_report_custom_entry *tmp = iter->next;
+
+        free(iter->value);
+        free(iter->key);
+        free(iter);
+
+        iter = tmp;
+    }
+
     free(report);
 }
 
+void
+sr_report_add_auth(struct sr_report *report, const char *key, const char *value)
+{
+    struct sr_report_custom_entry *new_entry = sr_malloc(sizeof(*new_entry));
+    new_entry->key = sr_strdup(key);
+    new_entry->value = sr_strdup(value);
+
+    /* prepend the new value
+     * it is much faster and easier
+     * we can do it because we do not to preserve the order(?) */
+    new_entry->next = report->auth_entries;
+    report->auth_entries = new_entry;
+}
+
 /* The object has to be non-empty, i.e. contain at least one key-value pair. */
 static void
 dismantle_object(char *obj)
@@ -221,6 +249,34 @@ sr_report_to_json(struct sr_report *report)
         free(rpms_str_indented);
     }
 
+    /* Custom entries.
+     *    "auth" : {   "foo": "blah"
+     *             ,   "one": "two"
+     *             }
+     */
+    struct sr_report_custom_entry *iter = report->auth_entries;
+    if (iter)
+    {
+        sr_strbuf_append_strf(strbuf, ",   \"auth\": {   ");
+        sr_json_append_escaped(strbuf, iter->key);
+        sr_strbuf_append_str(strbuf, ": ");
+        sr_json_append_escaped(strbuf, iter->value);
+        sr_strbuf_append_str(strbuf, "\n");
+
+        /* the first entry is prefix with '{', see lines above */
+        iter = iter->next;
+        while (iter)
+        {
+            sr_strbuf_append_strf(strbuf, "            ,   ");
+            sr_json_append_escaped(strbuf, iter->key);
+            sr_strbuf_append_str(strbuf, ": ");
+            sr_json_append_escaped(strbuf, iter->value);
+            sr_strbuf_append_str(strbuf, "\n");
+            iter = iter->next;
+        }
+        sr_strbuf_append_str(strbuf, "            } ");
+    }
+
     sr_strbuf_append_str(strbuf, "}");
     return sr_strbuf_free_nobuf(strbuf);
 }
@@ -336,6 +392,32 @@ sr_report_from_json(struct sr_json_value *root, char **error_message)
 
     }
 
+    /* Authentication entries. */
+    struct sr_json_value *extra = json_element(root, "auth");
+    if (extra)
+    {
+        if (!JSON_CHECK_TYPE(extra, SR_JSON_OBJECT, "auth"))
+            goto fail;
+
+        const unsigned children = json_object_children_count(extra);
+
+        /* from the last children down to the first for easier testing :)
+         * keep it as it is as long as sr_report_add_auth() does LIFO */
+        for (unsigned i = 1; i <= children; ++i)
+        {
+            const char *child_name = NULL;
+            struct sr_json_value *child_object = json_object_get_child(extra,
+                                                                       children - i,
+                                                                       &child_name);
+
+            if (!JSON_CHECK_TYPE(child_object, SR_JSON_STRING, child_name))
+                continue;
+
+            const char *child_value = json_string_get_value(child_object);
+            sr_report_add_auth(report, child_name, child_value);
+        }
+    }
+
     return report;
 
 fail:
diff --git a/python/py_report.c b/python/py_report.c
index d314027..8aa35dd 100644
--- a/python/py_report.c
+++ b/python/py_report.c
@@ -42,6 +42,8 @@
 #define to_json_doc "Usage: report.to_json()\n\n" \
                     "Returns: string - the report serialized as JSON"
 
+#define auth_doc "Dictinary of key/value pairs used for authentication"
+
 /* See python/py_common.h and python/py_gdb_frame.c for generic getters/setters documentation. */
 #define GSOFF_PY_STRUCT sr_py_report
 #define GSOFF_PY_MEMBER report
@@ -64,6 +66,7 @@ report_getset[] =
     SR_ATTRIBUTE_STRING(component_name,   "Name of the software component this report pertains to (string)"       ),
     { (char*)"report_version", sr_py_report_get_version, sr_py_setter_readonly, (char*)"Version of the report (int)", NULL },
     { (char*)"report_type", sr_py_report_get_type, sr_py_report_set_type, (char*)"Report type (string)", NULL },
+    { (char*)"auth",        sr_py_report_get_auth, sr_py_report_set_auth, (char*)auth_doc, NULL               },
     { NULL },
 };
 
@@ -492,3 +495,30 @@ sr_py_report_to_json(PyObject *self, PyObject *args)
     free(json);
     return result;
 }
+
+PyObject *
+sr_py_report_get_auth(PyObject *self, void *data)
+{
+    struct sr_report *report = ((struct sr_py_report *)self)->report;
+    struct sr_report_custom_entry *ae = report->auth_entries;
+
+    PyObject *auth = PyDict_New();
+    for (ae = report->auth_entries; ae; ae = ae->next)
+    {
+        PyObject *val = PyString_FromString(ae->value);
+        if (!val)
+            return NULL;
+
+        if (PyDict_SetItemString(auth, ae->key, val) == -1)
+            return NULL;
+    }
+
+    return auth;
+}
+
+int
+sr_py_report_set_auth(PyObject *self, PyObject *rhs, void *data)
+{
+    PyErr_SetString(PyExc_NotImplementedError, "Setting auth data is not implemented.");
+    return -1;
+}
diff --git a/python/py_report.h b/python/py_report.h
index a451dbc..88ec30f 100644
--- a/python/py_report.h
+++ b/python/py_report.h
@@ -65,6 +65,8 @@ PyObject *sr_py_report_str(PyObject *self);
 PyObject *sr_py_report_get_version(PyObject *self, void *data);
 PyObject *sr_py_report_get_type(PyObject *self, void *data);
 int sr_py_report_set_type(PyObject *self, PyObject *rhs, void *data);
+PyObject *sr_py_report_get_auth(PyObject *self, void *data);
+int sr_py_report_set_auth(PyObject *self, PyObject *rhs, void *data);
 
 /**
  * Methods.
diff --git a/tests/json_files/ureport-1-auth b/tests/json_files/ureport-1-auth
new file mode 100644
index 0000000..a8db2df
--- /dev/null
+++ b/tests/json_files/ureport-1-auth
@@ -0,0 +1,112 @@
+{
+  "ureport_version": 2,
+
+  "reason": "Program /usr/bin/sleep was terminated by signal 11",
+
+  "os": {
+    "name": "fedora",
+    "version": "18",
+    "architecture": "x86_64"
+  },
+
+  "problem": {
+    "type": "core",
+
+    "executable": "/usr/bin/sleep",
+
+    "signal": 11,
+
+    "component": "coreutils",
+
+    "user": {
+      "local": true,
+      "root": false
+    },
+
+    "stacktrace": [
+      {
+        "crash_thread": true,
+
+        "frames": [
+          {
+            "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
+            "build_id_offset": 767024,
+            "file_name": "/lib64/libc.so.6",
+            "address": 251315074096,
+            "fingerprint": "6c1eb9626919a2a5f6a4fc4c2edc9b21b33b7354",
+            "function_name": "__nanosleep"
+          },
+          {
+            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
+            "build_id_offset": 16567,
+            "file_name": "/usr/bin/sleep",
+            "address": 4210871,
+            "fingerprint": "d24433b82a2c751fc580f47154823e0bed641a54",
+            "function_name": "close_stdout"
+          },
+          {
+            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
+            "build_id_offset": 16202,
+            "file_name": "/usr/bin/sleep",
+            "address": 4210506,
+            "fingerprint": "562719fb960d1c4dbf30c04b3cff37c82acc3d2d",
+            "function_name": "close_stdout"
+          },
+          {
+            "build_id": "cd379d3bb5d07c96d491712e41c34bcd06b2ce32",
+            "build_id_offset": 6404,
+            "fingerprint": "2e8fb95adafe21d035b9bcb9993810fecf4be657",
+            "file_name": "/usr/bin/sleep",
+            "address": 4200708
+          },
+          {
+            "build_id": "5f6632d75fd027f5b7b410787f3f06c6bf73eee6",
+            "build_id_offset": 137733,
+            "file_name": "/lib64/libc.so.6",
+            "address": 251314444805,
+            "fingerprint": "075acda5d3230e115cf7c88597eaba416bdaa6bb",
+            "function_name": "__libc_start_main"
+          }
+        ]
+      }
+    ]
+  },
+
+  "packages": [
+    {
+      "name": "coreutils",
+      "epoch": 0,
+      "version": "8.17",
+      "architecture": "x86_64",
+      "package_role": "affected",
+      "release": "8.fc18",
+      "install_time": 1371464601
+    },
+    {
+      "name": "glibc",
+      "epoch": 0,
+      "version": "2.16",
+      "architecture": "x86_64",
+      "release": "31.fc18",
+      "install_time": 1371464176
+    },
+    {
+      "name": "glibc-common",
+      "epoch": 0,
+      "version": "2.16",
+      "architecture": "x86_64",
+      "release": "31.fc18",
+      "install_time": 1371464184
+    }
+  ],
+
+  "reporter": {
+    "version": "0.3",
+    "name": "satyr"
+  },
+
+  "auth": {
+    "hostname": "localhost",
+    "machine_id": "0000"
+  }
+}
diff --git a/tests/python/report.py b/tests/python/report.py
index 83fa76b..5f731b5 100755
--- a/tests/python/report.py
+++ b/tests/python/report.py
@@ -91,6 +91,12 @@ class TestReport(BindingsTestCase):
     def test_hash(self):
         self.assertHashable(self.report)
 
+    def test_auth(self):
+        self.assertFalse(self.report.auth)
+        self.assertRaises(NotImplementedError, self.report.__setattr__, 'auth', {'hostname': 'darkstar'})
+
+        report_with_auth = satyr.Report(load_input_contents('../json_files/ureport-1-auth'))
+        self.assertEqual(report_with_auth.auth, {'hostname': 'localhost', 'machine_id': '0000'})
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/report.at b/tests/report.at
index 8736137..1e773d2 100644
--- a/tests/report.at
+++ b/tests/report.at
@@ -57,3 +57,84 @@ int main(void)
   return 0;
 }
 ]])
+
+## ------------------ ##
+## sr_report_add_auth ##
+## ------------------ ##
+
+AT_TESTFUN([sr_report_add_auth],
+[[
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "report.h"
+
+void check_struct(struct sr_report *report, const char **expected)
+{
+    const char **exp_iter = expected;
+    struct sr_report_custom_entry *cust_iter = report->auth_entries;
+
+    while(cust_iter && *exp_iter)
+    {
+        fprintf(stdout, "Expected('%s':'%s') vs. Current('%s':'%s')\n",
+            exp_iter[0], exp_iter[1], cust_iter->key, cust_iter->value);
+
+        assert(strcmp(cust_iter->key, exp_iter[0]) == 0 &&
+               strcmp(cust_iter->value, exp_iter[1]) == 0);
+
+        cust_iter = cust_iter->next;
+        exp_iter += 2;
+    }
+
+    assert(cust_iter == NULL);
+    assert(*exp_iter == NULL);
+}
+
+void check_json(const char *json, const char **expected)
+{
+    const char **exp_iter = expected;
+    while (*exp_iter)
+    {
+        char *entry = NULL;
+        asprintf(&entry, "\"%s\": \"%s\"", exp_iter[0], exp_iter[1]);
+
+        fprintf(stdout, "Checking: '%s'\n", entry);
+
+        if (strstr(json, entry) == NULL)
+        {
+            fprintf(stderr, "JSON:\n%s\n", json);
+            abort();
+        }
+
+        exp_iter += 2;
+    }
+}
+
+
+int main(void)
+{
+  struct sr_report *report = sr_report_new();
+
+  sr_report_add_auth(report, "foo", "blah");
+  sr_report_add_auth(report, "abrt", "awesome");
+  sr_report_add_auth(report, "satyr", "wonderful");
+
+  const char *expected[] = { "satyr", "wonderful", "abrt", "awesome", "foo", "blah", NULL };
+
+  check_struct(report, expected);
+
+  sr_report_to_json(report);
+
+  char *json = sr_report_to_json(report);
+
+  check_json(json, expected);
+
+  char *error = NULL;
+  struct sr_report *copy = sr_report_from_json_text(json, &error);
+
+  check_struct(copy, expected);
+
+  return 0;
+}
+]])
-- 
1.9.3