36923b
From 80c64be80f2bffdcf5d2432e1e59d633fd68d516 Mon Sep 17 00:00:00 2001
36923b
From: Grace Chin <gchin@redhat.com>
36923b
Date: Mon, 13 Jun 2022 09:02:32 -0400
36923b
Subject: [PATCH 1/4] Add pcmk__is_user_in_group()
36923b
36923b
---
36923b
 lib/common/crmcommon_private.h |  3 +++
36923b
 lib/common/utils.c             | 33 +++++++++++++++++++++++++++++++++
36923b
 2 files changed, 36 insertions(+)
36923b
36923b
diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h
36923b
index 6b7be9c68..c2fcb0adf 100644
36923b
--- a/lib/common/crmcommon_private.h
36923b
+++ b/lib/common/crmcommon_private.h
36923b
@@ -96,6 +96,9 @@ void pcmk__free_acls(GList *acls);
36923b
 G_GNUC_INTERNAL
36923b
 void pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user);
36923b
 
36923b
+G_GNUC_INTERNAL
36923b
+bool pcmk__is_user_in_group(const char *user, const char *group);
36923b
+
36923b
 G_GNUC_INTERNAL
36923b
 void pcmk__apply_acl(xmlNode *xml);
36923b
 
36923b
diff --git a/lib/common/utils.c b/lib/common/utils.c
36923b
index 2dfbef278..f23583acb 100644
36923b
--- a/lib/common/utils.c
36923b
+++ b/lib/common/utils.c
36923b
@@ -27,6 +27,7 @@
36923b
 #include <time.h>
36923b
 #include <libgen.h>
36923b
 #include <signal.h>
36923b
+#include <grp.h>
36923b
 
36923b
 #include <qb/qbdefs.h>
36923b
 
36923b
@@ -53,6 +54,38 @@ gboolean crm_config_error = FALSE;
36923b
 gboolean crm_config_warning = FALSE;
36923b
 char *crm_system_name = NULL;
36923b
 
36923b
+bool
36923b
+pcmk__is_user_in_group(const char *user, const char *group)
36923b
+{
36923b
+    struct group *grent;
36923b
+    char **gr_mem;
36923b
+
36923b
+    if (user == NULL || group == NULL) {
36923b
+        return false;
36923b
+    }
36923b
+    
36923b
+    setgrent();
36923b
+    while ((grent = getgrent()) != NULL) {
36923b
+        if (grent->gr_mem == NULL) {
36923b
+            continue;
36923b
+        }
36923b
+
36923b
+        if(strcmp(group, grent->gr_name) != 0) {
36923b
+            continue;
36923b
+        }
36923b
+
36923b
+        gr_mem = grent->gr_mem;
36923b
+        while (*gr_mem != NULL) {
36923b
+            if (!strcmp(user, *gr_mem++)) {
36923b
+                endgrent();
36923b
+                return true;
36923b
+            }
36923b
+        }
36923b
+    }
36923b
+    endgrent();
36923b
+    return false;
36923b
+}
36923b
+
36923b
 int
36923b
 crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
36923b
 {
36923b
-- 
36923b
2.31.1
36923b
36923b
36923b
From 5fbe5c310de00390fb36d866823a7745ba4812e3 Mon Sep 17 00:00:00 2001
36923b
From: Grace Chin <gchin@redhat.com>
36923b
Date: Mon, 13 Jun 2022 09:04:57 -0400
36923b
Subject: [PATCH 2/4] Add unit test for pcmk__is_user_in_group()
36923b
36923b
---
36923b
 lib/common/Makefile.am                        |  2 +-
36923b
 lib/common/mock.c                             | 31 +++++--
36923b
 lib/common/mock_private.h                     | 11 +++
36923b
 lib/common/tests/acl/Makefile.am              | 11 ++-
36923b
 .../tests/acl/pcmk__is_user_in_group_test.c   | 92 +++++++++++++++++++
36923b
 5 files changed, 137 insertions(+), 10 deletions(-)
36923b
 create mode 100644 lib/common/tests/acl/pcmk__is_user_in_group_test.c
36923b
36923b
diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am
36923b
index d7aae53bf..04d56dc3c 100644
36923b
--- a/lib/common/Makefile.am
36923b
+++ b/lib/common/Makefile.am
36923b
@@ -94,7 +94,7 @@ libcrmcommon_la_SOURCES	+= watchdog.c
36923b
 libcrmcommon_la_SOURCES	+= xml.c
36923b
 libcrmcommon_la_SOURCES	+= xpath.c
36923b
 
36923b
-WRAPPED = calloc getenv getpwnam_r uname
36923b
+WRAPPED = calloc getenv getpwnam_r uname setgrent getgrent endgrent
36923b
 WRAPPED_FLAGS = $(foreach fn,$(WRAPPED),-Wl,--wrap=$(fn))
36923b
 
36923b
 libcrmcommon_test_la_SOURCES	= $(libcrmcommon_la_SOURCES)
36923b
diff --git a/lib/common/mock.c b/lib/common/mock.c
36923b
index 55812ddbc..fa9431e6d 100644
36923b
--- a/lib/common/mock.c
36923b
+++ b/lib/common/mock.c
36923b
@@ -11,6 +11,7 @@
36923b
 #include <stdlib.h>
36923b
 #include <sys/types.h>
36923b
 #include <sys/utsname.h>
36923b
+#include <grp.h>
36923b
 
36923b
 #include "mock_private.h"
36923b
 
36923b
@@ -18,13 +19,13 @@
36923b
  * libcrmcommon_test.a, not into libcrmcommon.so.  It is used to support
36923b
  * constructing mock versions of library functions for unit testing.
36923b
  *
36923b
- * Each unit test will only ever want to use a mocked version of one or two
36923b
- * library functions.  However, we need to mark all the mocked functions as
36923b
- * wrapped (with -Wl,--wrap= in the LDFLAGS) in libcrmcommon_test.a so that
36923b
- * all those unit tests can share the same special test library.  The unit
36923b
- * test then defines its own wrapped function.  Because a unit test won't
36923b
- * define every single wrapped function, there will be undefined references
36923b
- * at link time.
36923b
+ * Each unit test will only ever want to use a mocked version of a few
36923b
+ * library functions (i.e. not all of them). However, we need to mark all
36923b
+ * the mocked functions as wrapped (with -Wl,--wrap= in the LDFLAGS) in
36923b
+ * libcrmcommon_test.a so that all those unit tests can share the same
36923b
+ * special test library.  The unit test then defines its own wrapped
36923b
+ * function. Because a unit test won't define every single wrapped
36923b
+ * function, there will be undefined references at link time.
36923b
  *
36923b
  * This file takes care of those undefined references.  It defines a
36923b
  * wrapped version of every function that simply calls the real libc
36923b
@@ -74,3 +75,19 @@ int __attribute__((weak))
36923b
 __wrap_uname(struct utsname *buf) {
36923b
     return __real_uname(buf);
36923b
 }
36923b
+
36923b
+void __attribute__((weak))
36923b
+__wrap_setgrent(void) {
36923b
+    __real_setgrent();
36923b
+}
36923b
+
36923b
+struct group * __attribute__((weak))
36923b
+__wrap_getgrent(void) {
36923b
+    return __real_getgrent();
36923b
+}
36923b
+
36923b
+void __attribute__((weak))
36923b
+__wrap_endgrent(void) {
36923b
+    __real_endgrent();
36923b
+}
36923b
+
36923b
diff --git a/lib/common/mock_private.h b/lib/common/mock_private.h
36923b
index 3df7c9839..0c1134cc3 100644
36923b
--- a/lib/common/mock_private.h
36923b
+++ b/lib/common/mock_private.h
36923b
@@ -14,6 +14,7 @@
36923b
 #include <stdlib.h>
36923b
 #include <sys/types.h>
36923b
 #include <sys/utsname.h>
36923b
+#include <grp.h>
36923b
 
36923b
 /* This header is for the sole use of libcrmcommon_test. */
36923b
 
36923b
@@ -31,4 +32,14 @@ int __wrap_getpwnam_r(const char *name, struct passwd *pwd,
36923b
 int __real_uname(struct utsname *buf);
36923b
 int __wrap_uname(struct utsname *buf);
36923b
 
36923b
+void __real_setgrent(void);
36923b
+void __wrap_setgrent(void);
36923b
+
36923b
+struct group *__real_getgrent(void);
36923b
+struct group *__wrap_getgrent(void);
36923b
+
36923b
+void __real_endgrent(void);
36923b
+void __wrap_endgrent(void);
36923b
+
36923b
+
36923b
 #endif  // MOCK_PRIVATE__H
36923b
diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am
36923b
index 679c9cb8e..a73fc354c 100644
36923b
--- a/lib/common/tests/acl/Makefile.am
36923b
+++ b/lib/common/tests/acl/Makefile.am
36923b
@@ -1,19 +1,26 @@
36923b
 #
36923b
-# Copyright 2021 the Pacemaker project contributors
36923b
+# Copyright 2021-2022 the Pacemaker project contributors
36923b
 #
36923b
 # The version control history for this file may have further details.
36923b
 #
36923b
 # This source code is licensed under the GNU General Public License version 2
36923b
 # or later (GPLv2+) WITHOUT ANY WARRANTY.
36923b
 #
36923b
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
36923b
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/lib/common
36923b
 LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
36923b
 
36923b
+pcmk__is_user_in_group_test_LDADD = $(top_builddir)/lib/common/libcrmcommon_test.la -lcmocka
36923b
+pcmk__is_user_in_group_test_LDFLAGS = \
36923b
+    -Wl,--wrap=setgrent \
36923b
+    -Wl,--wrap=getgrent \
36923b
+    -Wl,--wrap=endgrent
36923b
+
36923b
 include $(top_srcdir)/mk/tap.mk
36923b
 
36923b
 # Add "_test" to the end of all test program names to simplify .gitignore.
36923b
 
36923b
 check_PROGRAMS = \
36923b
+    pcmk__is_user_in_group_test \
36923b
 	pcmk_acl_required_test \
36923b
     xml_acl_denied_test \
36923b
     xml_acl_enabled_test
36923b
diff --git a/lib/common/tests/acl/pcmk__is_user_in_group_test.c b/lib/common/tests/acl/pcmk__is_user_in_group_test.c
36923b
new file mode 100644
36923b
index 000000000..67b8c2c7c
36923b
--- /dev/null
36923b
+++ b/lib/common/tests/acl/pcmk__is_user_in_group_test.c
36923b
@@ -0,0 +1,92 @@
36923b
+/*
36923b
+ * Copyright 2020-2022 the Pacemaker project contributors
36923b
+ *
36923b
+ * The version control history for this file may have further details.
36923b
+ *
36923b
+ * This source code is licensed under the GNU Lesser General Public License
36923b
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
36923b
+ */
36923b
+
36923b
+#include <crm_internal.h>
36923b
+#include <crm/common/acl.h>
36923b
+#include "../../crmcommon_private.h"
36923b
+
36923b
+#include "mock_private.h"
36923b
+
36923b
+#include <stdarg.h>
36923b
+#include <stddef.h>
36923b
+#include <stdint.h>
36923b
+#include <setjmp.h>
36923b
+#include <cmocka.h>
36923b
+
36923b
+// THe index of the group that is going to be returned next from "get group entry" (getgrent)
36923b
+static int group_idx = 0;
36923b
+
36923b
+// Data used for testing
36923b
+static const char* grp0_members[] = {
36923b
+    "user0", "user1", NULL
36923b
+};
36923b
+
36923b
+static const char* grp1_members[] = {
36923b
+    "user1", NULL
36923b
+};
36923b
+
36923b
+static const char* grp2_members[] = {
36923b
+    "user2", "user1", NULL
36923b
+};
36923b
+
36923b
+// an array of "groups" (a struct from grp.h), the members of the groups are initalized here to some testing data.
36923b
+// Casting away the consts to make the compiler happy and simplify initialization. 
36923b
+// We never actually change these variables during the test!
36923b
+// string literal = const char* (cannot be changed b/c ? ) vs. char* (its getting casted to this)
36923b
+static const int NUM_GROUPS = 3;
36923b
+static struct group groups[] = {
36923b
+    {(char*)"grp0", (char*)"", 0, (char**)grp0_members},
36923b
+    {(char*)"grp1", (char*)"", 1, (char**)grp1_members},
36923b
+    {(char*)"grp2", (char*)"", 2, (char**)grp2_members},
36923b
+};
36923b
+
36923b
+// This function resets the group_idx to 0.
36923b
+void
36923b
+__wrap_setgrent(void) {
36923b
+    group_idx = 0;
36923b
+}
36923b
+
36923b
+// This function returns the next group entry in the list of groups, or
36923b
+// NULL if there aren't any left.
36923b
+// group_idx is a global variable which keeps track of where you are in the list
36923b
+struct group *
36923b
+__wrap_getgrent(void) {
36923b
+    if(group_idx >= NUM_GROUPS) return NULL;
36923b
+    return &groups[group_idx++];
36923b
+}
36923b
+
36923b
+void
36923b
+__wrap_endgrent(void) {
36923b
+}
36923b
+
36923b
+static void
36923b
+is_pcmk__is_user_in_group(void **state)
36923b
+{
36923b
+    // null user
36923b
+    assert_false(pcmk__is_user_in_group(NULL, "grp0"));
36923b
+    // null group
36923b
+    assert_false(pcmk__is_user_in_group("user0", NULL));
36923b
+    // nonexistent group
36923b
+    assert_false(pcmk__is_user_in_group("user0", "nonexistent_group"));
36923b
+    // user is in group
36923b
+    assert_true(pcmk__is_user_in_group("user0", "grp0"));
36923b
+    // user is not in group
36923b
+    assert_false(pcmk__is_user_in_group("user2", "grp0"));
36923b
+}
36923b
+
36923b
+int
36923b
+main(int argc, char **argv)
36923b
+{
36923b
+    const struct CMUnitTest tests[] = {
36923b
+        cmocka_unit_test(is_pcmk__is_user_in_group)
36923b
+    };
36923b
+
36923b
+    cmocka_set_message_output(CM_OUTPUT_TAP);
36923b
+    return cmocka_run_group_tests(tests, NULL, NULL);
36923b
+}
36923b
-- 
36923b
2.31.1
36923b
36923b
36923b
From 1bb7fda60f5b8547d7457f20543b7e50089cf06b Mon Sep 17 00:00:00 2001
36923b
From: Grace Chin <gchin@redhat.com>
36923b
Date: Mon, 13 Jun 2022 09:17:36 -0400
36923b
Subject: [PATCH 3/4] Add ACL group support
36923b
36923b
closes T61
36923b
---
36923b
 lib/common/acl.c | 7 +++++++
36923b
 1 file changed, 7 insertions(+)
36923b
36923b
diff --git a/lib/common/acl.c b/lib/common/acl.c
36923b
index f68069bbd..d7f8469b1 100644
36923b
--- a/lib/common/acl.c
36923b
+++ b/lib/common/acl.c
36923b
@@ -320,6 +320,13 @@ pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
36923b
                         crm_debug("Unpacking ACLs for user '%s'", id);
36923b
                         p->acls = parse_acl_entry(acls, child, p->acls);
36923b
                     }
36923b
+                } else if (!strcmp(tag, XML_ACL_TAG_GROUP)) {
36923b
+                    const char *id = crm_element_value(child, XML_ATTR_ID);
36923b
+
36923b
+                    if (id && pcmk__is_user_in_group(user,id)) {
36923b
+                        crm_debug("Unpacking ACLs for group '%s'", id);
36923b
+                        p->acls = parse_acl_entry(acls, child, p->acls);
36923b
+                    }
36923b
                 }
36923b
             }
36923b
         }
36923b
-- 
36923b
2.31.1
36923b
36923b
36923b
From f4efd55d9424d34908ba3e2bcffe16c00b2cf660 Mon Sep 17 00:00:00 2001
36923b
From: Grace Chin <gchin@redhat.com>
36923b
Date: Mon, 13 Jun 2022 09:20:36 -0400
36923b
Subject: [PATCH 4/4] Allow acl_target and acl_group elements to take a 'name'
36923b
 attribute to use a name different from 'id'
36923b
36923b
closes T60
36923b
---
36923b
 include/crm/msg_xml.h |  1 +
36923b
 lib/common/acl.c      | 21 +++++++++++++++++----
36923b
 2 files changed, 18 insertions(+), 4 deletions(-)
36923b
36923b
diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h
36923b
index b36dcf060..6470520b1 100644
36923b
--- a/include/crm/msg_xml.h
36923b
+++ b/include/crm/msg_xml.h
36923b
@@ -133,6 +133,7 @@ extern "C" {
36923b
 #  define XML_ATTR_VERSION		"version"
36923b
 #  define XML_ATTR_DESC			"description"
36923b
 #  define XML_ATTR_ID			"id"
36923b
+#  define XML_ATTR_NAME			"name"
36923b
 #  define XML_ATTR_IDREF			"id-ref"
36923b
 #  define XML_ATTR_ID_LONG		"long-id"
36923b
 #  define XML_ATTR_TYPE			"type"
36923b
diff --git a/lib/common/acl.c b/lib/common/acl.c
36923b
index d7f8469b1..b9f7472ee 100644
36923b
--- a/lib/common/acl.c
36923b
+++ b/lib/common/acl.c
36923b
@@ -278,8 +278,13 @@ pcmk__apply_acl(xmlNode *xml)
36923b
 
36923b
 /*!
36923b
  * \internal
36923b
- * \brief Unpack ACLs for a given user
36923b
- *
36923b
+ * \brief Unpack ACLs for a given user into the
36923b
+ * metadata of the target XML tree
36923b
+ * 
36923b
+ * Taking the description of ACLs from the source XML tree and 
36923b
+ * marking up the target XML tree with access information for the
36923b
+ * given user by tacking it onto the relevant nodes
36923b
+ * 
36923b
  * \param[in]     source  XML with ACL definitions
36923b
  * \param[in,out] target  XML that ACLs will be applied to
36923b
  * \param[in]     user    Username whose ACLs need to be unpacked
36923b
@@ -314,14 +319,22 @@ pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
36923b
 
36923b
                 if (!strcmp(tag, XML_ACL_TAG_USER)
36923b
                     || !strcmp(tag, XML_ACL_TAG_USERv1)) {
36923b
-                    const char *id = crm_element_value(child, XML_ATTR_ID);
36923b
+                    const char *id = crm_element_value(child, XML_ATTR_NAME);
36923b
+
36923b
+                    if (id == NULL) {
36923b
+                        id = crm_element_value(child, XML_ATTR_ID);
36923b
+                    }
36923b
 
36923b
                     if (id && strcmp(id, user) == 0) {
36923b
                         crm_debug("Unpacking ACLs for user '%s'", id);
36923b
                         p->acls = parse_acl_entry(acls, child, p->acls);
36923b
                     }
36923b
                 } else if (!strcmp(tag, XML_ACL_TAG_GROUP)) {
36923b
-                    const char *id = crm_element_value(child, XML_ATTR_ID);
36923b
+                    const char *id = crm_element_value(child, XML_ATTR_NAME);
36923b
+
36923b
+                    if (id == NULL) {
36923b
+                        id = crm_element_value(child, XML_ATTR_ID);
36923b
+                    }
36923b
 
36923b
                     if (id && pcmk__is_user_in_group(user,id)) {
36923b
                         crm_debug("Unpacking ACLs for group '%s'", id);
36923b
-- 
36923b
2.31.1
36923b