From 3e11b3c67bc31662810f72571311d7d7580e3f0a Mon Sep 17 00:00:00 2001 From: Jakub Filak 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 Signed-off-by: Martin Milata mmilata: s/custom/auth/g, python bindings tests: add a test for uReport auth data Related to #171 Signed-off-by: Jakub Filak Signed-off-by: Martin Milata 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 --- 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 +#include +#include +#include +#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