2 Unit tests for the dsdb group auditing code in group_audit.c
4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 int ldb_group_audit_log_module_init(const char *version);
27 #include "../group_audit.c"
29 #include "lib/ldb/include/ldb_private.h"
33 * Mock version of dsdb_search_one
35 struct ldb_dn *g_basedn = NULL;
36 enum ldb_scope g_scope;
37 const char * const *g_attrs = NULL;
38 uint32_t g_dsdb_flags;
39 const char *g_exp_fmt;
40 const char *g_dn = NULL;
41 int g_status = LDB_SUCCESS;
43 int dsdb_search_one(struct ldb_context *ldb,
45 struct ldb_message **msg,
46 struct ldb_dn *basedn,
48 const char * const *attrs,
50 const char *exp_fmt, ...) _PRINTF_ATTRIBUTE(8, 9)
52 struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb, g_dn);
53 struct ldb_message *m = talloc_zero(mem_ctx, struct ldb_message);
60 g_dsdb_flags = dsdb_flags;
67 * Mock version of audit_log_json
70 #define MAX_EXPECTED_MESSAGES 16
71 static struct json_object messages[MAX_EXPECTED_MESSAGES];
72 static size_t messages_sent = 0;
74 void audit_message_send(
75 struct imessaging_context *msg_ctx,
76 const char *server_name,
77 uint32_t message_type,
78 struct json_object *message)
80 messages[messages_sent].root = json_deep_copy(message->root);
81 messages[messages_sent].valid = message->valid;
85 #define check_group_change_message(m, u, a)\
86 _check_group_change_message(m, u, a, __FILE__, __LINE__);
88 * declare the internal cmocka cm_print_error so that we can output messages
91 void cm_print_error(const char * const format, ...);
94 * Validate a group change JSON audit message
96 * It should contain 3 elements.
97 * Have a type of "groupChange"
98 * Have a groupChange element
100 * The group change element should have 10 elements.
102 * There should be a user element matching the expected value
103 * There should be an action matching the expected value
105 static void _check_group_change_message(
112 struct json_object json;
113 json_t *audit = NULL;
116 json = messages[message];
119 * Validate the root JSON element
120 * check the number of elements
122 if (json_object_size(json.root) != 3) {
124 "Unexpected number of elements in root %zu != %d\n",
125 json_object_size(json.root),
131 * Check the type element
133 v = json_object_get(json.root, "type");
135 cm_print_error( "No \"type\" element\n");
139 value = json_string_value(v);
140 if (strncmp("groupChange", value, strlen("groupChange") != 0)) {
142 "Unexpected type \"%s\" != \"groupChange\"\n",
148 audit = json_object_get(json.root, "groupChange");
150 cm_print_error("No groupChange element\n");
155 * Validate the groupChange element
157 if (json_object_size(audit) != 10) {
159 "Unexpected number of elements in groupChange "
161 json_object_size(audit),
166 * Validate the user element
168 v = json_object_get(audit, "user");
170 cm_print_error( "No user element\n");
174 value = json_string_value(v);
175 if (strncmp(user, value, strlen(user) != 0)) {
177 "Unexpected user name \"%s\" != \"%s\"\n",
184 * Validate the action element
186 v = json_object_get(audit, "action");
188 cm_print_error( "No action element\n");
192 value = json_string_value(v);
193 if (strncmp(action, value, strlen(action) != 0)) {
195 "Unexpected action \"%s\" != \"%s\"\n",
203 * Test helper to check ISO 8601 timestamps for validity
205 static void check_timestamp(time_t before, const char *timestamp)
218 * Convert the ISO 8601 timestamp into a time_t
219 * Note for convenience we ignore the value of the microsecond
220 * part of the time stamp.
224 "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d",
234 assert_int_equal(9, rc);
235 tm.tm_year = tm.tm_year - 1900;
236 tm.tm_mon = tm.tm_mon - 1;
238 actual = mktime(&tm);
241 * The timestamp should be before <= actual <= after
243 assert_true(difftime(actual, before) >= 0);
244 assert_true(difftime(after, actual) >= 0);
248 * Test helper to validate a version object.
250 static void check_version(struct json_t *version, int major, int minor)
252 struct json_t *v = NULL;
254 assert_true(json_is_object(version));
255 assert_int_equal(2, json_object_size(version));
257 v = json_object_get(version, "major");
259 assert_int_equal(major, json_integer_value(v));
261 v = json_object_get(version, "minor");
263 assert_int_equal(minor, json_integer_value(v));
267 * Test helper to insert a transaction_id into a request.
269 static void add_transaction_id(struct ldb_request *req, const char *id)
272 struct dsdb_control_transaction_identifier *transaction_id = NULL;
274 transaction_id = talloc_zero(
276 struct dsdb_control_transaction_identifier);
277 assert_non_null(transaction_id);
278 GUID_from_string(id, &guid);
279 transaction_id->transaction_guid = guid;
280 ldb_request_add_control(
282 DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID,
288 * Test helper to add a session id and user SID
290 static void add_session_data(
292 struct ldb_context *ldb,
294 const char *user_sid)
296 struct auth_session_info *sess = NULL;
297 struct security_token *token = NULL;
298 struct dom_sid *sid = NULL;
299 struct GUID session_id;
302 sess = talloc_zero(ctx, struct auth_session_info);
303 token = talloc_zero(ctx, struct security_token);
304 sid = talloc_zero(ctx, struct dom_sid);
305 ok = string_to_sid(sid, user_sid);
308 sess->security_token = token;
309 GUID_from_string(session, &session_id);
310 sess->unique_session_token = session_id;
311 ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
314 static void test_get_transaction_id(void **state)
316 struct ldb_request *req = NULL;
318 const char * const ID = "7130cb06-2062-6a1b-409e-3514c26b1773";
319 char *guid_str = NULL;
320 struct GUID_txt_buf guid_buff;
323 TALLOC_CTX *ctx = talloc_new(NULL);
327 * No transaction id, should return a zero guid
329 req = talloc_zero(ctx, struct ldb_request);
330 guid = get_transaction_id(req);
335 * And now test with the transaction_id set
337 req = talloc_zero(ctx, struct ldb_request);
338 assert_non_null(req);
339 add_transaction_id(req, ID);
341 guid = get_transaction_id(req);
342 guid_str = GUID_buf_string(guid, &guid_buff);
343 assert_string_equal(ID, guid_str);
349 static void test_audit_group_hr(void **state)
351 struct ldb_context *ldb = NULL;
352 struct ldb_module *module = NULL;
353 struct ldb_request *req = NULL;
355 struct tsocket_address *ts = NULL;
357 const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
358 const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
360 struct GUID transaction_id;
361 const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
365 const char *rs = NULL;
370 TALLOC_CTX *ctx = talloc_new(NULL);
372 ldb = ldb_init(ctx, NULL);
374 GUID_from_string(TRANSACTION, &transaction_id);
376 module = talloc_zero(ctx, struct ldb_module);
379 tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
380 ldb_set_opaque(ldb, "remoteAddress", ts);
382 add_session_data(ctx, ldb, SESSION, SID);
384 req = talloc_zero(ctx, struct ldb_request);
385 req->operation = LDB_ADD;
386 add_transaction_id(req, TRANSACTION);
388 line = audit_group_human_readable(
395 LDB_ERR_OPERATIONS_ERROR);
396 assert_non_null(line);
398 rs = "\\[the-action\\] at \\["
400 "\\] status \\[Operations error\\] "
401 "Remote host \\[ipv4:127.0.0.1:0\\] "
402 "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] "
403 "Group \\[the-group-name\\] "
404 "User \\[the-user-name\\]";
406 ret = regcomp(®ex, rs, 0);
407 assert_int_equal(0, ret);
409 ret = regexec(®ex, line, 0, NULL, 0);
410 assert_int_equal(0, ret);
418 * test get_parsed_dns
419 * For this test we assume Valgrind or Address Sanitizer will detect any over
420 * runs. Also we don't care that the values are DN's only that the value in the
421 * element is copied to the parsed_dns.
423 static void test_get_parsed_dns(void **state)
425 struct ldb_message_element *el = NULL;
426 struct parsed_dn *dns = NULL;
428 TALLOC_CTX *ctx = talloc_new(NULL);
430 el = talloc_zero(ctx, struct ldb_message_element);
433 * empty element, zero dns
435 dns = get_parsed_dns(ctx, el);
442 el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
443 el->values[0] = data_blob_string_const("The first value");
445 dns = get_parsed_dns(ctx, el);
447 assert_ptr_equal(el->values[0].data, dns[0].v->data);
448 assert_int_equal(el->values[0].length, dns[0].v->length);
457 el = talloc_zero(ctx, struct ldb_message_element);
459 el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
460 el->values[0] = data_blob_string_const("The first value");
461 el->values[0] = data_blob_string_const("The second value");
463 dns = get_parsed_dns(ctx, el);
465 assert_ptr_equal(el->values[0].data, dns[0].v->data);
466 assert_int_equal(el->values[0].length, dns[0].v->length);
468 assert_ptr_equal(el->values[1].data, dns[1].v->data);
469 assert_int_equal(el->values[1].length, dns[1].v->length);
474 static void test_dn_compare(void **state)
477 struct ldb_context *ldb = NULL;
486 TALLOC_CTX *ctx = talloc_new(NULL);
487 const struct GUID *ZERO_GUID = talloc_zero(ctx, struct GUID);
489 ldb = ldb_init(ctx, NULL);
490 ldb_register_samba_handlers(ldb);
494 * Identical binary DN's
496 ab = data_blob_string_const(
497 "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;"
498 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
499 a = talloc_zero(ctx, struct parsed_dn);
502 bb = data_blob_string_const(
503 "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;"
504 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
505 b = talloc_zero(ctx, struct parsed_dn);
508 res = dn_compare(ctx, ldb, a, b);
509 assert_int_equal(BINARY_EQUAL, res);
511 * DN's should not have been parsed
513 assert_null(a->dsdb_dn);
514 assert_memory_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
515 assert_null(b->dsdb_dn);
516 assert_memory_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
522 * differing binary DN's but equal GUID's
524 ab = data_blob_string_const(
525 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
526 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
527 a = talloc_zero(ctx, struct parsed_dn);
530 bb = data_blob_string_const(
531 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
532 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
533 b = talloc_zero(ctx, struct parsed_dn);
536 res = dn_compare(ctx, ldb, a, b);
537 assert_int_equal(EQUAL, res);
539 * DN's should have been parsed
541 assert_non_null(a->dsdb_dn);
542 assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
543 assert_non_null(b->dsdb_dn);
544 assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
550 * differing binary DN's but and second guid greater
552 ab = data_blob_string_const(
553 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;"
554 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
555 a = talloc_zero(ctx, struct parsed_dn);
558 bb = data_blob_string_const(
559 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
560 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
561 b = talloc_zero(ctx, struct parsed_dn);
564 res = dn_compare(ctx, ldb, a, b);
565 assert_int_equal(LESS_THAN, res);
567 * DN's should have been parsed
569 assert_non_null(a->dsdb_dn);
570 assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
571 assert_non_null(b->dsdb_dn);
572 assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
578 * differing binary DN's but and second guid less
580 ab = data_blob_string_const(
581 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;"
582 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
583 a = talloc_zero(ctx, struct parsed_dn);
586 bb = data_blob_string_const(
587 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651c>;"
588 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
589 b = talloc_zero(ctx, struct parsed_dn);
592 res = dn_compare(ctx, ldb, a, b);
593 assert_int_equal(GREATER_THAN, res);
595 * DN's should have been parsed
597 assert_non_null(a->dsdb_dn);
598 assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
599 assert_non_null(b->dsdb_dn);
600 assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
608 static void test_get_primary_group_dn(void **state)
611 struct ldb_context *ldb = NULL;
612 struct ldb_module *module = NULL;
613 const uint32_t RID = 71;
615 const char *SID = "S-1-5-21-2470180966-3899876309-2637894779";
616 const char *DN = "OU=Things,DC=ad,DC=testing,DC=samba,DC=org";
619 TALLOC_CTX *ctx = talloc_new(NULL);
621 ldb = ldb_init(ctx, NULL);
622 ldb_register_samba_handlers(ldb);
624 module = talloc_zero(ctx, struct ldb_module);
628 * Pass an empty dom sid this will cause dom_sid_split_rid to fail;
629 * assign to sid.num_auths to suppress a valgrind warning.
632 dn = get_primary_group_dn(ctx, module, &sid, RID);
638 assert_true(string_to_sid(&sid, SID));
640 dn = get_primary_group_dn(ctx, module, &sid, RID);
642 assert_string_equal(DN, dn);
643 assert_int_equal(LDB_SCOPE_BASE, g_scope);
644 assert_int_equal(0, g_dsdb_flags);
645 assert_null(g_attrs);
646 assert_null(g_exp_fmt);
648 ("<SID=S-1-5-21-2470180966-3899876309-71>",
649 ldb_dn_get_extended_linearized(ctx, g_basedn, 1));
652 * Test dsdb search failure
654 g_status = LDB_ERR_NO_SUCH_OBJECT;
655 dn = get_primary_group_dn(ctx, module, &sid, RID);
662 static void test_audit_group_json(void **state)
664 struct ldb_context *ldb = NULL;
665 struct ldb_module *module = NULL;
666 struct ldb_request *req = NULL;
668 struct tsocket_address *ts = NULL;
670 const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
671 const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
673 struct GUID transaction_id;
674 const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
677 struct json_object json;
678 json_t *audit = NULL;
684 TALLOC_CTX *ctx = talloc_new(NULL);
686 ldb = ldb_init(ctx, NULL);
688 GUID_from_string(TRANSACTION, &transaction_id);
690 module = talloc_zero(ctx, struct ldb_module);
693 tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
694 ldb_set_opaque(ldb, "remoteAddress", ts);
696 add_session_data(ctx, ldb, SESSION, SID);
698 req = talloc_zero(ctx, struct ldb_request);
699 req->operation = LDB_ADD;
700 add_transaction_id(req, TRANSACTION);
703 json = audit_group_json(
709 LDB_ERR_OPERATIONS_ERROR);
710 assert_int_equal(3, json_object_size(json.root));
712 v = json_object_get(json.root, "type");
714 assert_string_equal("groupChange", json_string_value(v));
716 v = json_object_get(json.root, "timestamp");
718 assert_true(json_is_string(v));
719 check_timestamp(before, json_string_value(v));
721 audit = json_object_get(json.root, "groupChange");
722 assert_non_null(audit);
723 assert_true(json_is_object(audit));
724 assert_int_equal(10, json_object_size(audit));
726 o = json_object_get(audit, "version");
728 check_version(o, AUDIT_MAJOR, AUDIT_MINOR);
730 v = json_object_get(audit, "statusCode");
732 assert_true(json_is_integer(v));
733 assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v));
735 v = json_object_get(audit, "status");
737 assert_true(json_is_string(v));
738 assert_string_equal("Operations error", json_string_value(v));
740 v = json_object_get(audit, "user");
742 assert_true(json_is_string(v));
743 assert_string_equal("the-user-name", json_string_value(v));
745 v = json_object_get(audit, "group");
747 assert_true(json_is_string(v));
748 assert_string_equal("the-group-name", json_string_value(v));
750 v = json_object_get(audit, "action");
752 assert_true(json_is_string(v));
753 assert_string_equal("the-action", json_string_value(v));
759 static void setup_ldb(
761 struct ldb_context **ldb,
762 struct ldb_module **module,
767 struct tsocket_address *ts = NULL;
768 struct audit_context *context = NULL;
770 *ldb = ldb_init(ctx, NULL);
771 ldb_register_samba_handlers(*ldb);
774 *module = talloc_zero(ctx, struct ldb_module);
775 (*module)->ldb = *ldb;
777 context = talloc_zero(*module, struct audit_context);
778 context->send_events = true;
779 context->msg_ctx = (struct imessaging_context *) 0x01;
781 ldb_module_set_private(*module, context);
783 tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
784 ldb_set_opaque(*ldb, "remoteAddress", ts);
786 add_session_data(ctx, *ldb, session, sid);
790 * Test the removal of a user from a group.
792 * The new element contains one group member
793 * The old element contains two group member
795 * Expect to see the removed entry logged.
797 * This test confirms bug 13664
798 * https://bugzilla.samba.org/show_bug.cgi?id=13664
800 static void test_log_membership_changes_removed(void **state)
802 struct ldb_context *ldb = NULL;
803 struct ldb_module *module = NULL;
804 const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
805 const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
806 const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
807 const char * const IP = "127.0.0.1";
808 struct ldb_request *req = NULL;
809 struct ldb_message_element *new_el = NULL;
810 struct ldb_message_element *old_el = NULL;
812 TALLOC_CTX *ctx = talloc_new(NULL);
814 setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
817 * Build the ldb_request
819 req = talloc_zero(ctx, struct ldb_request);
820 req->operation = LDB_ADD;
821 add_transaction_id(req, TRANSACTION);
824 * Populate the new elements, containing one entry.
825 * Indicating that one element has been removed
827 new_el = talloc_zero(ctx, struct ldb_message_element);
828 new_el->num_values = 1;
829 new_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
830 new_el->values[0] = data_blob_string_const(
831 "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
832 "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
833 "DC=example,DC=com");
836 * Populate the old elements, with two elements
837 * The first is the same as the one in new elements.
839 old_el = talloc_zero(ctx, struct ldb_message_element);
840 old_el->num_values = 2;
841 old_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
842 old_el->values[0] = data_blob_string_const(
843 "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
844 "cn=grpadttstuser01,cn=users,DC=addom,"
845 "DC=samba,DC=example,DC=com");
846 old_el->values[1] = data_blob_string_const(
847 "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
848 "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
849 "DC=example,DC=com");
852 * call log_membership_changes
855 log_membership_changes(module, req, new_el, old_el, status);
860 assert_int_equal(1, messages_sent);
862 check_group_change_message(
864 "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com",
870 json_free(&messages[0]);
875 * Note: to run under valgrind us:
876 * valgrind --suppressions=test_group_audit.valgrind bin/test_group_audit
877 * This suppresses the errors generated because the ldb_modules are not
882 const struct CMUnitTest tests[] = {
883 cmocka_unit_test(test_audit_group_json),
884 cmocka_unit_test(test_get_transaction_id),
885 cmocka_unit_test(test_audit_group_hr),
886 cmocka_unit_test(test_get_parsed_dns),
887 cmocka_unit_test(test_dn_compare),
888 cmocka_unit_test(test_get_primary_group_dn),
889 cmocka_unit_test(test_log_membership_changes_removed),
892 cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
893 return cmocka_run_group_tests(tests, NULL, NULL);