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",
202 #define check_timestamp(b, t)\
203 _check_timestamp(b, t, __FILE__, __LINE__);
205 * Test helper to check ISO 8601 timestamps for validity
207 static void _check_timestamp(
209 const char *timestamp,
224 * Convert the ISO 8601 timestamp into a time_t
225 * Note for convenience we ignore the value of the microsecond
226 * part of the time stamp.
230 "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d",
240 assert_int_equal(9, rc);
241 tm.tm_year = tm.tm_year - 1900;
242 tm.tm_mon = tm.tm_mon - 1;
244 actual = mktime(&tm);
247 * The time stamp should be before <= actual <= after
249 if (difftime(actual, before) < 0) {
256 "time stamp \"%s\" is before start time \"%s\"\n",
261 if (difftime(after, actual) < 0) {
268 "time stamp \"%s\" is after finish time \"%s\"\n",
276 * Test helper to validate a version object.
278 static void check_version(struct json_t *version, int major, int minor)
280 struct json_t *v = NULL;
282 assert_true(json_is_object(version));
283 assert_int_equal(2, json_object_size(version));
285 v = json_object_get(version, "major");
287 assert_int_equal(major, json_integer_value(v));
289 v = json_object_get(version, "minor");
291 assert_int_equal(minor, json_integer_value(v));
295 * Test helper to insert a transaction_id into a request.
297 static void add_transaction_id(struct ldb_request *req, const char *id)
300 struct dsdb_control_transaction_identifier *transaction_id = NULL;
302 transaction_id = talloc_zero(
304 struct dsdb_control_transaction_identifier);
305 assert_non_null(transaction_id);
306 GUID_from_string(id, &guid);
307 transaction_id->transaction_guid = guid;
308 ldb_request_add_control(
310 DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID,
316 * Test helper to add a session id and user SID
318 static void add_session_data(
320 struct ldb_context *ldb,
322 const char *user_sid)
324 struct auth_session_info *sess = NULL;
325 struct security_token *token = NULL;
326 struct dom_sid *sid = NULL;
327 struct GUID session_id;
330 sess = talloc_zero(ctx, struct auth_session_info);
331 token = talloc_zero(ctx, struct security_token);
332 sid = talloc_zero(ctx, struct dom_sid);
333 ok = string_to_sid(sid, user_sid);
336 sess->security_token = token;
337 GUID_from_string(session, &session_id);
338 sess->unique_session_token = session_id;
339 ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
342 static void test_get_transaction_id(void **state)
344 struct ldb_request *req = NULL;
346 const char * const ID = "7130cb06-2062-6a1b-409e-3514c26b1773";
347 char *guid_str = NULL;
348 struct GUID_txt_buf guid_buff;
351 TALLOC_CTX *ctx = talloc_new(NULL);
355 * No transaction id, should return a zero guid
357 req = talloc_zero(ctx, struct ldb_request);
358 guid = get_transaction_id(req);
363 * And now test with the transaction_id set
365 req = talloc_zero(ctx, struct ldb_request);
366 assert_non_null(req);
367 add_transaction_id(req, ID);
369 guid = get_transaction_id(req);
370 guid_str = GUID_buf_string(guid, &guid_buff);
371 assert_string_equal(ID, guid_str);
377 static void test_audit_group_hr(void **state)
379 struct ldb_context *ldb = NULL;
380 struct ldb_module *module = NULL;
381 struct ldb_request *req = NULL;
383 struct tsocket_address *ts = NULL;
385 const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
386 const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
388 struct GUID transaction_id;
389 const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
393 const char *rs = NULL;
398 TALLOC_CTX *ctx = talloc_new(NULL);
400 ldb = ldb_init(ctx, NULL);
402 GUID_from_string(TRANSACTION, &transaction_id);
404 module = talloc_zero(ctx, struct ldb_module);
407 tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
408 ldb_set_opaque(ldb, "remoteAddress", ts);
410 add_session_data(ctx, ldb, SESSION, SID);
412 req = talloc_zero(ctx, struct ldb_request);
413 req->operation = LDB_ADD;
414 add_transaction_id(req, TRANSACTION);
416 line = audit_group_human_readable(
423 LDB_ERR_OPERATIONS_ERROR);
424 assert_non_null(line);
426 rs = "\\[the-action\\] at \\["
428 "\\] status \\[Operations error\\] "
429 "Remote host \\[ipv4:127.0.0.1:0\\] "
430 "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] "
431 "Group \\[the-group-name\\] "
432 "User \\[the-user-name\\]";
434 ret = regcomp(®ex, rs, 0);
435 assert_int_equal(0, ret);
437 ret = regexec(®ex, line, 0, NULL, 0);
438 assert_int_equal(0, ret);
446 * test get_parsed_dns
447 * For this test we assume Valgrind or Address Sanitizer will detect any over
448 * runs. Also we don't care that the values are DN's only that the value in the
449 * element is copied to the parsed_dns.
451 static void test_get_parsed_dns(void **state)
453 struct ldb_message_element *el = NULL;
454 struct parsed_dn *dns = NULL;
456 TALLOC_CTX *ctx = talloc_new(NULL);
458 el = talloc_zero(ctx, struct ldb_message_element);
461 * empty element, zero dns
463 dns = get_parsed_dns(ctx, el);
470 el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
471 el->values[0] = data_blob_string_const("The first value");
473 dns = get_parsed_dns(ctx, el);
475 assert_ptr_equal(el->values[0].data, dns[0].v->data);
476 assert_int_equal(el->values[0].length, dns[0].v->length);
485 el = talloc_zero(ctx, struct ldb_message_element);
487 el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
488 el->values[0] = data_blob_string_const("The first value");
489 el->values[0] = data_blob_string_const("The second value");
491 dns = get_parsed_dns(ctx, el);
493 assert_ptr_equal(el->values[0].data, dns[0].v->data);
494 assert_int_equal(el->values[0].length, dns[0].v->length);
496 assert_ptr_equal(el->values[1].data, dns[1].v->data);
497 assert_int_equal(el->values[1].length, dns[1].v->length);
502 static void test_dn_compare(void **state)
505 struct ldb_context *ldb = NULL;
514 TALLOC_CTX *ctx = talloc_new(NULL);
515 const struct GUID *ZERO_GUID = talloc_zero(ctx, struct GUID);
517 ldb = ldb_init(ctx, NULL);
518 ldb_register_samba_handlers(ldb);
522 * Identical binary DN's
524 ab = data_blob_string_const(
525 "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;"
526 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
527 a = talloc_zero(ctx, struct parsed_dn);
530 bb = data_blob_string_const(
531 "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;"
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(BINARY_EQUAL, res);
539 * DN's should not have been parsed
541 assert_null(a->dsdb_dn);
542 assert_memory_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
543 assert_null(b->dsdb_dn);
544 assert_memory_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
550 * differing binary DN's but equal GUID's
552 ab = data_blob_string_const(
553 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
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(EQUAL, 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 greater
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-166ed0c2651e>;"
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(LESS_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));
606 * differing binary DN's but and second guid less
608 ab = data_blob_string_const(
609 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;"
610 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
611 a = talloc_zero(ctx, struct parsed_dn);
614 bb = data_blob_string_const(
615 "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651c>;"
616 "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
617 b = talloc_zero(ctx, struct parsed_dn);
620 res = dn_compare(ctx, ldb, a, b);
621 assert_int_equal(GREATER_THAN, res);
623 * DN's should have been parsed
625 assert_non_null(a->dsdb_dn);
626 assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
627 assert_non_null(b->dsdb_dn);
628 assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
636 static void test_get_primary_group_dn(void **state)
639 struct ldb_context *ldb = NULL;
640 struct ldb_module *module = NULL;
641 const uint32_t RID = 71;
643 const char *SID = "S-1-5-21-2470180966-3899876309-2637894779";
644 const char *DN = "OU=Things,DC=ad,DC=testing,DC=samba,DC=org";
647 TALLOC_CTX *ctx = talloc_new(NULL);
649 ldb = ldb_init(ctx, NULL);
650 ldb_register_samba_handlers(ldb);
652 module = talloc_zero(ctx, struct ldb_module);
656 * Pass an empty dom sid this will cause dom_sid_split_rid to fail;
657 * assign to sid.num_auths to suppress a valgrind warning.
660 dn = get_primary_group_dn(ctx, module, &sid, RID);
666 assert_true(string_to_sid(&sid, SID));
668 dn = get_primary_group_dn(ctx, module, &sid, RID);
670 assert_string_equal(DN, dn);
671 assert_int_equal(LDB_SCOPE_BASE, g_scope);
672 assert_int_equal(0, g_dsdb_flags);
673 assert_null(g_attrs);
674 assert_null(g_exp_fmt);
676 ("<SID=S-1-5-21-2470180966-3899876309-71>",
677 ldb_dn_get_extended_linearized(ctx, g_basedn, 1));
680 * Test dsdb search failure
682 g_status = LDB_ERR_NO_SUCH_OBJECT;
683 dn = get_primary_group_dn(ctx, module, &sid, RID);
690 static void test_audit_group_json(void **state)
692 struct ldb_context *ldb = NULL;
693 struct ldb_module *module = NULL;
694 struct ldb_request *req = NULL;
696 struct tsocket_address *ts = NULL;
698 const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
699 const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
701 struct GUID transaction_id;
702 const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
705 struct json_object json;
706 json_t *audit = NULL;
712 TALLOC_CTX *ctx = talloc_new(NULL);
714 ldb = ldb_init(ctx, NULL);
716 GUID_from_string(TRANSACTION, &transaction_id);
718 module = talloc_zero(ctx, struct ldb_module);
721 tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
722 ldb_set_opaque(ldb, "remoteAddress", ts);
724 add_session_data(ctx, ldb, SESSION, SID);
726 req = talloc_zero(ctx, struct ldb_request);
727 req->operation = LDB_ADD;
728 add_transaction_id(req, TRANSACTION);
731 json = audit_group_json(
737 LDB_ERR_OPERATIONS_ERROR);
738 assert_int_equal(3, json_object_size(json.root));
740 v = json_object_get(json.root, "type");
742 assert_string_equal("groupChange", json_string_value(v));
744 v = json_object_get(json.root, "timestamp");
746 assert_true(json_is_string(v));
747 check_timestamp(before, json_string_value(v));
749 audit = json_object_get(json.root, "groupChange");
750 assert_non_null(audit);
751 assert_true(json_is_object(audit));
752 assert_int_equal(10, json_object_size(audit));
754 o = json_object_get(audit, "version");
756 check_version(o, AUDIT_MAJOR, AUDIT_MINOR);
758 v = json_object_get(audit, "statusCode");
760 assert_true(json_is_integer(v));
761 assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v));
763 v = json_object_get(audit, "status");
765 assert_true(json_is_string(v));
766 assert_string_equal("Operations error", json_string_value(v));
768 v = json_object_get(audit, "user");
770 assert_true(json_is_string(v));
771 assert_string_equal("the-user-name", json_string_value(v));
773 v = json_object_get(audit, "group");
775 assert_true(json_is_string(v));
776 assert_string_equal("the-group-name", json_string_value(v));
778 v = json_object_get(audit, "action");
780 assert_true(json_is_string(v));
781 assert_string_equal("the-action", json_string_value(v));
787 static void setup_ldb(
789 struct ldb_context **ldb,
790 struct ldb_module **module,
795 struct tsocket_address *ts = NULL;
796 struct audit_context *context = NULL;
798 *ldb = ldb_init(ctx, NULL);
799 ldb_register_samba_handlers(*ldb);
802 *module = talloc_zero(ctx, struct ldb_module);
803 (*module)->ldb = *ldb;
805 context = talloc_zero(*module, struct audit_context);
806 context->send_events = true;
807 context->msg_ctx = (struct imessaging_context *) 0x01;
809 ldb_module_set_private(*module, context);
811 tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
812 ldb_set_opaque(*ldb, "remoteAddress", ts);
814 add_session_data(ctx, *ldb, session, sid);
818 * Test the removal of a user from a group.
820 * The new element contains one group member
821 * The old element contains two group member
823 * Expect to see the removed entry logged.
825 * This test confirms bug 13664
826 * https://bugzilla.samba.org/show_bug.cgi?id=13664
828 static void test_log_membership_changes_removed(void **state)
830 struct ldb_context *ldb = NULL;
831 struct ldb_module *module = NULL;
832 const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
833 const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
834 const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
835 const char * const IP = "127.0.0.1";
836 struct ldb_request *req = NULL;
837 struct ldb_message_element *new_el = NULL;
838 struct ldb_message_element *old_el = NULL;
840 TALLOC_CTX *ctx = talloc_new(NULL);
842 setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
845 * Build the ldb_request
847 req = talloc_zero(ctx, struct ldb_request);
848 req->operation = LDB_ADD;
849 add_transaction_id(req, TRANSACTION);
852 * Populate the new elements, containing one entry.
853 * Indicating that one element has been removed
855 new_el = talloc_zero(ctx, struct ldb_message_element);
856 new_el->num_values = 1;
857 new_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
858 new_el->values[0] = data_blob_string_const(
859 "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
860 "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
861 "DC=example,DC=com");
864 * Populate the old elements, with two elements
865 * The first is the same as the one in new elements.
867 old_el = talloc_zero(ctx, struct ldb_message_element);
868 old_el->num_values = 2;
869 old_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
870 old_el->values[0] = data_blob_string_const(
871 "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
872 "cn=grpadttstuser01,cn=users,DC=addom,"
873 "DC=samba,DC=example,DC=com");
874 old_el->values[1] = data_blob_string_const(
875 "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
876 "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
877 "DC=example,DC=com");
880 * call log_membership_changes
883 log_membership_changes(module, req, new_el, old_el, status);
888 assert_int_equal(1, messages_sent);
890 check_group_change_message(
892 "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com",
898 json_free(&messages[0]);
903 * Note: to run under valgrind us:
904 * valgrind --suppressions=test_group_audit.valgrind bin/test_group_audit
905 * This suppresses the errors generated because the ldb_modules are not
910 const struct CMUnitTest tests[] = {
911 cmocka_unit_test(test_audit_group_json),
912 cmocka_unit_test(test_get_transaction_id),
913 cmocka_unit_test(test_audit_group_hr),
914 cmocka_unit_test(test_get_parsed_dns),
915 cmocka_unit_test(test_dn_compare),
916 cmocka_unit_test(test_get_primary_group_dn),
917 cmocka_unit_test(test_log_membership_changes_removed),
920 cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
921 return cmocka_run_group_tests(tests, NULL, NULL);