1 /*
2 * Copyright 2004-2025 the Pacemaker project contributors
3 *
4 * The version control history for this file may have further details.
5 *
6 * This source code is licensed under the GNU Lesser General Public License
7 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 */
9
10 #include <crm_internal.h>
11
12 #include <stdbool.h>
13 #include <stdint.h> // uint32_t
14 #include <stdio.h>
15 #include <sys/types.h>
16 #include <pwd.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20
21 #include <libxml/tree.h> // xmlNode, etc.
22 #include <libxml/xmlstring.h> // xmlChar
23 #include <libxml/xpath.h> // xmlXPathObject, etc.
24
25 #include <crm/crm.h>
26 #include <crm/common/xml.h>
27 #include "crmcommon_private.h"
28
29 typedef struct {
30 enum pcmk__xml_flags mode;
31 gchar *xpath;
32 } xml_acl_t;
33
34 /*!
35 * \internal
36 * \brief Free an \c xml_acl_t object
37 *
38 * \param[in,out] data \c xml_acl_t object to free
39 *
40 * \note This is a \c GDestroyNotify function.
41 */
42 static void
43 free_acl(void *data)
44 {
45 xml_acl_t *acl = data;
46
47 if (acl == NULL) {
48 return;
49 }
50
51 g_free(acl->xpath);
52 free(acl);
53 }
54
55 /*!
56 * \internal
57 * \brief Free a list of \c xml_acl_t objects
58 *
59 * \param[in,out] acls List of \c xml_acl_t objects
60 */
61 void
62 pcmk__free_acls(GList *acls)
63 {
64 g_list_free_full(acls, free_acl);
65 }
66
67 /*!
68 * \internal
69 * \brief Get readable description of an ACL mode
70 *
71 * \param[in] mode ACL mode (one of \c pcmk__xf_acl_read,
72 * \c pcmk__xf_acl_write, \c pcmk__xf_acl_deny, or
73 * \c pcmk__xf_acl_create
74 *
75 * \return Static string describing \p mode, or \c "none" if \p mode is invalid
76 */
77 static const char *
78 acl_mode_text(enum pcmk__xml_flags mode)
79 {
80 switch (mode) {
81 case pcmk__xf_acl_read:
82 return "read";
83
84 case pcmk__xf_acl_create:
85 case pcmk__xf_acl_write:
86 return "read/write";
87
88 case pcmk__xf_acl_deny:
89 return "deny";
90
91 default:
92 return "none";
93 }
94 }
95
96 /*!
97 * \internal
98 * \brief Parse an ACL mode from a string
99 *
100 * \param[in] text String to parse
101 *
102 * \return ACL mode corresponding to \p text
103 */
104 static enum pcmk__xml_flags
105 parse_acl_mode(const char *text)
106 {
107 if (pcmk__str_eq(text, PCMK_VALUE_READ, pcmk__str_none)) {
108 return pcmk__xf_acl_read;
109 }
110
111 if (pcmk__str_eq(text, PCMK_VALUE_WRITE, pcmk__str_none)) {
112 return pcmk__xf_acl_write;
113 }
114
115 if (pcmk__str_eq(text, PCMK_VALUE_DENY, pcmk__str_none)) {
116 return pcmk__xf_acl_deny;
117 }
118
119 return pcmk__xf_none;
120 }
121
122 /*!
123 * \internal
124 * \brief Set a config warning if ACL permission specifiers are mismatched
125 *
126 * The schema requires exactly one of \c PCMK_XA_XPATH, \c PCMK_XA_REFERENCE,
127 * or \c PCMK_XA_OBJECT_TYPE. Additionally, \c PCMK_XA_ATTRIBUTE may be used
128 * only with \c PCMK_XA_OBJECT_TYPE.
129 *
130 * We've handled these in a very permissive and inconsistent manner thus far. To
131 * avoid breaking backward compatibility, the best we can do for now is to set
132 * configuration warnings and log how we will behave if the specifiers are set
133 * incorrectly.
134 *
135 * The caller has already ensured that at least one of \c PCMK_XA_XPATH,
136 * \c PCMK_XA_REFERENCE, or \c PCMK_XA_OBJECT_TYPE is set.
137 */
138 static void
139 warn_on_specifier_mismatch(const xmlNode *xml, const char *xpath,
140 const char *ref, const char *tag, const char *attr)
141 {
142 // @COMPAT Let's be more strict at a compatibility break... please...
143
144 const char *id = pcmk__s(pcmk__xe_id(xml), "without ID");
145 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
146 const char *parent_type = (const char *) xml->parent->name;
147
148 if ((xpath != NULL) && (ref == NULL) && (tag == NULL) && (attr == NULL)) {
149 return;
150 }
151
152 if ((xpath == NULL) && (ref != NULL) && (tag == NULL) && (attr == NULL)) {
153 return;
154 }
155
156 if ((xpath == NULL) && (ref == NULL) && (tag != NULL)) {
157 return;
158 }
159
160 // Remaining cases are not possible with schema validation enabled
161
162 if (xpath != NULL) {
163 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s "
164 "(in <%s> %s) has " PCMK_XA_XPATH " set along with "
165 PCMK_XA_REFERENCE ", " PCMK_XA_OBJECT_TYPE ", or "
166 PCMK_XA_ATTRIBUTE ". Using " PCMK_XA_XPATH " and "
167 "ignoring the rest.", id, parent_type, parent_id);
168 return;
169 }
170
171 // Log both of the below if appropriate
172
173 if ((tag == NULL) && (attr != NULL)) {
174 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s "
175 "(in <%s> %s) has " PCMK_XA_ATTRIBUTE " set without "
176 PCMK_XA_OBJECT_TYPE ". Using '*' for "
177 PCMK_XA_OBJECT_TYPE ".", id, parent_type, parent_id);
178 }
179
180 if (ref != NULL) {
181 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s "
182 "(in <%s> %s) has " PCMK_XA_REFERENCE " set along "
183 "with " PCMK_XA_OBJECT_TYPE " or "
184 PCMK_XA_ATTRIBUTE ". Using all of these criteria "
185 "together, but support may be removed in a future "
186 "release.", id, parent_type, parent_id);
187 }
188 }
189
190 /*!
191 * \internal
192 * \brief Create an ACL based on an ACL permission XML element
193 *
194 * The \c PCMK_XE_ACL_PERMISSION element should have already been validated for
195 * unrecoverable schema violations when this function is called. There may be
196 * recoverable violations, but the element should be well-formed enough to
197 * create an \c xml_acl_t.
198 *
199 * \param[in] xml \c PCMK_XE_ACL_PERMISSION element
200 * \param[in] mode One of \c pcmk__xf_acl_read, \c pcmk__xf_acl_write, or
201 * \c pcmk__xf_acl_deny
202 *
203 * \return Newly allocated ACL object (guaranteed not to be \c NULL)
204 *
205 * \note The caller is responsible for freeing the return value using
206 * \c free_acl().
207 */
208 static xml_acl_t *
209 create_acl(const xmlNode *xml, enum pcmk__xml_flags mode)
210 {
211 const char *tag = pcmk__xe_get(xml, PCMK_XA_OBJECT_TYPE);
212 const char *ref = pcmk__xe_get(xml, PCMK_XA_REFERENCE);
213 const char *xpath = pcmk__xe_get(xml, PCMK_XA_XPATH);
214 const char *attr = pcmk__xe_get(xml, PCMK_XA_ATTRIBUTE);
215
216 GString *buf = NULL;
217 xml_acl_t *acl = pcmk__assert_alloc(1, sizeof (xml_acl_t));
218
219 warn_on_specifier_mismatch(xml, xpath, ref, tag, attr);
220
221 acl->mode = mode;
222
223 if (xpath != NULL) {
224 acl->xpath = g_strdup(xpath);
225 return acl;
226 }
227
228 buf = g_string_sized_new(128);
229
230 g_string_append_printf(buf, "//%s", pcmk__s(tag, "*"));
231
232 if ((ref != NULL) && (attr != NULL)) {
233 // Not possible with schema validation enabled
234 g_string_append_printf(buf, "[@" PCMK_XA_ID "='%s' and @%s]", ref,
235 attr);
236
237 } else if (ref != NULL) {
238 g_string_append_printf(buf, "[@" PCMK_XA_ID "='%s']", ref);
239
240 } else if (attr != NULL) {
241 g_string_append_printf(buf, "[@%s]", attr);
242 }
243
244 acl->xpath = g_string_free(buf, FALSE);
245 return acl;
246 }
247
248 /*!
249 * \internal
250 * \brief Unpack a \c PCMK_XE_ACL_PERMISSION element to an \c xml_acl_t
251 *
252 * Append the new \c xml_acl_t object to a list.
253 *
254 * \param[in] xml Permission element to unpack
255 * \param[in,out] user_data List of ACLs to append to (<tt>GList **</tt>)
256 *
257 * \return \c pcmk_rc_ok (to keep iterating)
258 *
259 * \note The caller is responsible for freeing \p *user_data using
260 * \c pcmk__free_acls().
261 * \note This is used as a callback for \c pcmk__xe_foreach_child().
262 */
263 static int
264 unpack_acl_permission(xmlNode *xml, void *user_data)
265 {
266 GList **acls = user_data;
267 const char *id = pcmk__xe_id(xml);
268 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
269 const char *parent_type = (const char *) xml->parent->name;
270
271 const char *kind_s = pcmk__xe_get(xml, PCMK_XA_KIND);
272 enum pcmk__xml_flags kind = pcmk__xf_none;
273 xml_acl_t *acl = NULL;
274
275 if (id == NULL) {
276 // Not possible with schema validation enabled
277 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element in <%s> %s has "
278 "no " PCMK_XA_ID " attribute", parent_type,
279 parent_id);
280
281 // Set a value to use for logging and continue unpacking
282 id = "without ID";
283 }
284
285 if (kind_s == NULL) {
286 // Not possible with schema validation enabled
287 pcmk__config_err("Ignoring <" PCMK_XE_ACL_PERMISSION "> element %s "
288 "(in <%s> %s) with no " PCMK_XA_KIND " attribute", id,
289 parent_type, parent_id);
290 return pcmk_rc_ok;
291 }
292
293 kind = parse_acl_mode(kind_s);
294 if (kind == pcmk__xf_none) {
295 // Not possible with schema validation enabled
296 pcmk__config_err("Ignoring <" PCMK_XE_ACL_PERMISSION "> element %s "
297 "(in <%s> %s) with unknown ACL kind '%s'", id,
298 parent_type, parent_id, kind_s);
299 return pcmk_rc_ok;
300 }
301
302 if ((pcmk__xe_get(xml, PCMK_XA_OBJECT_TYPE) == NULL)
303 && (pcmk__xe_get(xml, PCMK_XA_REFERENCE) == NULL)
304 && (pcmk__xe_get(xml, PCMK_XA_XPATH) == NULL)) {
305
306 // Not possible with schema validation enabled
307 pcmk__config_err("Ignoring <" PCMK_XE_ACL_PERMISSION "> element %s "
308 "(in <%s> %s) without selection criteria. Exactly one "
309 "of the following attributes is required: "
310 PCMK_XA_OBJECT_TYPE ", " PCMK_XA_REFERENCE ", "
311 PCMK_XA_XPATH ".", id, parent_type, parent_id);
312 return pcmk_rc_ok;
313 }
314
315 acl = create_acl(xml, kind);
316 *acls = g_list_append(*acls, acl);
317
318 pcmk__trace("Unpacked <" PCMK_XE_ACL_PERMISSION "> element %s "
319 "(in <%s> %s) with " PCMK_XA_KIND "='%s' as XPath '%s'", id,
320 parent_type, parent_id, kind_s, acl->xpath);
321
322 return pcmk_rc_ok;
323 }
324
325 /*!
326 * \internal
327 * \brief Get the ACL role whose ID matches a role reference
328 *
329 * If there are multiple matches (not allowed by the schema), return the first
330 * one for backward compatibility and set a config warning.
331 *
332 * \param[in] xml \c PCMK_XE_ROLE element (an ACL role reference)
333 *
334 * \return \c PCMK_XE_ACL_ROLE element whose \c PCMK_XA_ID attribute matches
335 * that of \p xml, or \c NULL if none is found
336 */
337 static xmlNode *
338 resolve_acl_role_ref(xmlNode *xml)
339 {
340 const char *id = pcmk__xe_id(xml);
341 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
342 const char *parent_type = (const char *) xml->parent->name;
343
344 xmlNode *result = NULL;
345 char *xpath = pcmk__assert_asprintf("//" PCMK_XE_ACL_ROLE
346 "[@" PCMK_XA_ID "='%s']", id);
347 xmlXPathObject *xpath_obj = pcmk__xpath_search(xml->doc, xpath);
348 const int num_results = pcmk__xpath_num_results(xpath_obj);
349
350 switch (num_results) {
351 case 0:
352 // Caller calls pcmk__config_err()
353 break;
354
355 case 1:
356 // Success
357 result = pcmk__xpath_result(xpath_obj, 0);
358 break;
359
360 default:
361 /* Not possible with schema validation enabled.
362 *
363 * @COMPAT At a compatibility break, use pcmk__xpath_find_one(),
364 * treat this as an error, and return NULL. For now, return the
365 * first match.
366 */
367 result = pcmk__xpath_result(xpath_obj, 0);
368 pcmk__config_warn("Multiple <" PCMK_XE_ACL_ROLE "> elements have "
369 PCMK_XA_ID "='%s'. Returning the first one for "
370 "<" PCMK_XE_ROLE "> in <%s> %s.", id, parent_type,
371 parent_id);
372 break;
373 }
374
375 free(xpath);
376 xmlXPathFreeObject(xpath_obj);
377 return result;
378 }
379
380 /*!
381 * \internal
382 * \brief Unpack an ACL role reference to a list of \c xml_acl_t
383 *
384 * Unpack a \c PCMK_XE_ROLE element within a \c PCMK_XE_ACL_TARGET or
385 * \c PCMK_XE_ACL_GROUP element. This element is a role reference. Its
386 * \c PCMK_XA_ID attribute is an IDREF; it must match the ID of a
387 * \c PCMK_XE_ACL_ROLE child of the \c PCMK_XE_ACLS element.
388 *
389 * The referenced \c PCMK_XE_ACL_ROLE contains zero or more
390 * \c PCMK_XE_ACL_PERMISSION children. Unpack those children to \c xml_acl_t
391 * objects and append them to a list.
392 *
393 * \param[in] xml Role reference element to unpack
394 * \param[in,out] acls List of ACLs to append to (\c NULL to start a new list)
395 *
396 * \return On success, \p acls with the new items appended, or a new list
397 * containing only the new items if \p acls is \c NULL. On failure,
398 * \p acls (unmodified).
399 *
400 * \note The caller is responsible for freeing the return value using
401 * \c pcmk__free_acls().
402 */
403 static GList *
404 unpack_acl_role_ref(xmlNode *xml, GList *acls)
405 {
406 const char *id = pcmk__xe_id(xml);
407 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
408 const char *parent_type = (const char *) xml->parent->name;
409
410 xmlNode *role = NULL;
411
412 if (id == NULL) {
413 // Not possible with schema validation enabled
414 pcmk__config_err("Ignoring <" PCMK_XE_ROLE "> element in <%s> %s with "
415 "no " PCMK_XA_ID " attribute", parent_type, parent_id);
416
417 // There is no reference role ID to match and unpack
418 return acls;
419 }
420
421 role = resolve_acl_role_ref(xml);
422 if (role == NULL) {
423 // Not possible with schema validation enabled
424 pcmk__config_err("Ignoring <" PCMK_XE_ROLE "> element %s in <%s> %s: "
425 "no <" PCMK_XE_ACL_ROLE "> with matching "
426 PCMK_XA_ID " found", id, parent_type, parent_id);
427 return acls;
428 }
429
430 pcmk__trace("Unpacking role '%s' referenced in <%s> element %s", id,
431 parent_type, parent_id);
432
433 pcmk__xe_foreach_child(role, PCMK_XE_ACL_PERMISSION, unpack_acl_permission,
434 &acls);
435 return acls;
436 }
437
438 /*!
439 * \internal
440 * \brief Unpack a child of an ACL target or group to a list of \c xml_acl_t
441 *
442 * \param[in] xml Child of a \c PCMK_XE_ACL_TARGET or
443 * \c PCMK_XE_ACL_GROUP
444 * \param[in,out] user_data List of ACLs to append to (<tt>GList **</tt>)
445 *
446 * \return \c pcmk_rc_ok (to keep iterating)
447 *
448 * \note The caller is responsible for freeing \p *user_data using
449 * \c pcmk__free_acls().
450 * \note This is used as a callback for \c pcmk__xe_foreach_child().
451 */
452 static int
453 unpack_acl_role_ref_or_perm(xmlNode *xml, void *user_data)
454 {
455 GList **acls = user_data;
456 const char *id = pcmk__s(pcmk__xe_id(xml), "without ID");
457 const char *parent_id = pcmk__s(pcmk__xe_id(xml->parent), "without ID");
458 const char *parent_type = (const char *) xml->parent->name;
459
460 if (pcmk__xe_is(xml, PCMK_XE_ROLE)) {
461 *acls = unpack_acl_role_ref(xml, *acls);
462 return pcmk_rc_ok;
463 }
464
465 if (!pcmk__xe_is(xml, PCMK_XE_ACL_PERMISSION)) {
466 return pcmk_rc_ok;
467 }
468
469 /* Not possible with schema validation enabled.
470 *
471 * @COMPAT Drop this support at a compatibility break. A PCMK_XE_ACL_TARGET
472 * or PCMK_XE_ACL_GROUP element should contain only PCMK_XE_ROLE elements as
473 * children.
474 */
475 pcmk__config_warn("<" PCMK_XE_ACL_PERMISSION "> element %s is a child of "
476 "<%s> %s. It should be a child of an "
477 "<" PCMK_XE_ACL_ROLE ">, and the parent should reference "
478 "that <" PCMK_XE_ACL_ROLE ">.", id, parent_type,
479 parent_id);
480
481 unpack_acl_permission(xml, acls);
482 return pcmk_rc_ok;
483 }
484
485 /*!
486 * \internal
487 * \brief Unpack an ACL target (user) element to a list of \c xml_acl_t
488 *
489 * \param[in] xml \c PCMK_XE_ACL_TARGET element to unpack
490 * \param[in,out] docpriv XML document private data whose \c acls field to
491 * append to
492 */
493 static void
494 unpack_acl_target(xmlNode *xml, xml_doc_private_t *docpriv)
495 {
496 const char *id = pcmk__s(pcmk__xe_get(xml, PCMK_XA_NAME), pcmk__xe_id(xml));
497
498 if (id == NULL) {
499 // Not possible with schema validation enabled
500 pcmk__config_err("Ignoring <" PCMK_XE_ACL_TARGET "> element with no "
501 PCMK_XA_NAME " or " PCMK_XA_ID " attribute");
502
503 // There is no user ID for the current ACL user to match
504 return;
505 }
506
507 if (!pcmk__str_eq(id, docpriv->acl_user, pcmk__str_none)) {
508 return;
509 }
510
511 pcmk__trace("Unpacking ACLs for user '%s'", id);
512 pcmk__xe_foreach_child(xml, NULL, unpack_acl_role_ref_or_perm,
513 &docpriv->acls);
514 }
515
516 /*!
517 * \internal
518 * \brief Unpack an ACL group element to a list of \c xml_acl_t
519 *
520 * \param[in] xml \c PCMK_XE_ACL_TARGET element to unpack
521 * \param[in,out] docpriv XML document private data whose \c acls field to
522 * append to
523 */
524 static void
525 unpack_acl_group(xmlNode *xml, xml_doc_private_t *docpriv)
526 {
527 const char *id = pcmk__s(pcmk__xe_get(xml, PCMK_XA_NAME), pcmk__xe_id(xml));
528
529 if (id == NULL) {
530 // Not possible with schema validation enabled
531 pcmk__config_err("Ignoring <" PCMK_XE_ACL_GROUP "> element with no "
532 PCMK_XA_NAME " or " PCMK_XA_ID " attribute");
533
534 // There is no group ID for the current ACL user to match
535 return;
536 }
537
538 if (!pcmk__is_user_in_group(docpriv->acl_user, id)) {
539 return;
540 }
541
542 pcmk__trace("Unpacking ACLs for group '%s' (user '%s')", id,
543 docpriv->acl_user);
544 pcmk__xe_foreach_child(xml, NULL, unpack_acl_role_ref_or_perm,
545 &docpriv->acls);
546 }
547
548 /*!
549 * \internal
550 * \brief Unpack an ACL target (user) or group element to a list of \c xml_acl_t
551 *
552 * \param[in] xml Element to unpack (\c PCMK_XE_ACL_TARGET
553 * or \c PCMK_XE_ACL_GROUP)
554 * \param[in,out] user_data XML document private data whose \c acls field to
555 * append to (<tt>xml_doc_private_t *</tt>)
556 *
557 * \return \c pcmk_rc_ok (to keep iterating)
558 *
559 * \note The caller is responsible for freeing \p user_data->acls using
560 * \c pcmk__free_acls().
561 * \note This is used as a callback for \c pcmk__xe_foreach_child().
562 */
563 static int
564 unpack_acl_target_or_group(xmlNode *xml, void *user_data)
565 {
566 if (pcmk__xe_is(xml, PCMK_XE_ACL_TARGET)) {
567 unpack_acl_target(xml, user_data);
568 return pcmk_rc_ok;
569 }
570
571 if (pcmk__xe_is(xml, PCMK_XE_ACL_GROUP)) {
572 unpack_acl_group(xml, user_data);
573 return pcmk_rc_ok;
574 }
575
576 return pcmk_rc_ok;
577 }
578
579 /*!
580 * \internal
581 * \brief Add a user's ACLs to a target XML document's private data
582 *
583 * Unpack the ACLs that apply to the user from the \c PCMK_XE_ACLS element in
584 * the source document to the \c acls list in the target document. If that list
585 * is already non-empty or if the user doesn't require ACLs, do nothing.
586 *
587 * Also set the target document's \c acl_user field to the given user.
588 *
589 * \param[in] source XML document whose ACL definitions to use
590 * \param[in,out] target XML document private data whose \c acls field to set
591 * \param[in] user User whose ACLs to unpack
592 */
593 void
594 pcmk__unpack_acls(xmlDoc *source, xml_doc_private_t *target, const char *user)
595 {
596 xmlNode *acls = NULL;
597
598 pcmk__assert(target != NULL);
599
600 if ((target->acls != NULL) || !pcmk_acl_required(user)) {
601 return;
602 }
603
604 pcmk__str_update(&target->acl_user, user);
605
606 acls = pcmk__xpath_find_one(source, "//" PCMK_XE_ACLS, PCMK__LOG_NEVER);
607 pcmk__xe_foreach_child(acls, NULL, unpack_acl_target_or_group, target);
608 }
609
610 /*!
611 * \internal
612 * \brief Set an ACL's mode on a node that matches its XPath expression
613 *
614 * Given a node that matches an ACL's XPath expression, get the corresponding
615 * XML element. Then set the ACL's mode flag in the private data of that
616 * element. For details, see comment in the body below, as well as the doc
617 * comment for \c pcmk__xpath_match_element().
618 *
619 * \param[in,out] match Node matched by the ACL's XPath expression
620 * \param[in] user_data ACL object (<tt>xml_acl_t *</tt>)
621 */
622 static void
623 apply_acl_to_match(xmlNode *match, void *user_data)
624 {
625 const xml_acl_t *acl = user_data;
626 xml_node_private_t *nodepriv = NULL;
627 GString *path = NULL;
628
629 /* @COMPAT If the ACL's XPath matches a node that is neither an element nor
630 * a document, we apply the ACL to the parent element rather than to the
631 * matched node. For example, if the XPath matches a "score" attribute, then
632 * it applies to every element that contains a "score" attribute. That is,
633 * the XPath expression "//@score" matches all attributes named "score", but
634 * we apply the ACL to all elements containing such an attribute.
635 *
636 * This behavior is incorrect from an XPath standpoint and is thus confusing
637 * and counterintuitive. The correct way to match all elements containing a
638 * "score" attribute is to use an XPath predicate: "// *[@score]". (Space
639 * inserted after slashes so that GCC doesn't throw an error about nested
640 * comments.)
641 *
642 * Additionally, if an XPath expression matches the entire document (for
643 * example, "/"), then the ACL applies to the document's root element if it
644 * exists.
645 *
646 * These behaviors should be changed so that the ACL applies to the nodes
647 * matched by the XPath expression, or so that it doesn't apply at all if
648 * applying an ACL to an attribute doesn't make sense.
649 *
650 * Unfortunately, we document in Pacemaker Explained that matching
651 * attributes is a valid way to match elements: "Attributes may be specified
652 * in the XPath to select particular elements, but the permissions apply to
653 * the entire element."
654 *
655 * So we have to keep this behavior at least until a compatibility break.
656 * Even then, it's not feasible in the general case to transform such XPath
657 * expressions using XSLT.
658 */
659 match = pcmk__xpath_match_element(match);
660 if (match == NULL) {
661 return;
662 }
663
664 nodepriv = match->_private;
665 pcmk__set_xml_flags(nodepriv, acl->mode);
666
667 path = pcmk__element_xpath(match);
668 pcmk__trace("Applied %s ACL to %s matched by %s", acl_mode_text(acl->mode),
669 path->str, acl->xpath);
670 g_string_free(path, TRUE);
671 }
672
673 /*!
674 * \internal
675 * \brief Apply an ACL to each matching node in an XML document
676 *
677 * See \c apply_acl_to_match() for details on applying the ACL.
678 *
679 * \param[in,out] data ACL object to apply (<tt>xml_acl_t *</tt>)
680 * \param[in,out] user_data XML document to match against (<tt>xmlDoc *</tt>)
681 */
682 static void
683 apply_acl_to_doc(gpointer data, gpointer user_data)
684 {
685 xml_acl_t *acl = data;
686 xmlDoc *doc = user_data;
687
688 pcmk__xpath_foreach_result(doc, acl->xpath, apply_acl_to_match, acl);
689 }
690
691 /*!
692 * \internal
693 * \brief Apply all of an XML document's ACLs
694 *
695 * For each ACL in the document's \c acls list, search the document for nodes
696 * that match the ACL's XPath expression. Then apply the ACL to each matching
697 * node.
698 *
699 * See \c apply_acl_to_doc() and \c apply_acl_to_match() for details.
700 *
701 * \param[in,out] doc XML document
702 */
703 void
704 pcmk__apply_acls(xmlDoc *doc)
705 {
706 xml_doc_private_t *docpriv = NULL;
707
708 pcmk__assert(doc != NULL);
709 docpriv = doc->_private;
710
711 if (!pcmk__xml_doc_all_flags_set(doc, pcmk__xf_acl_enabled)) {
712 return;
713 }
714
715 g_list_foreach(docpriv->acls, apply_acl_to_doc, doc);
716 }
717
718 /*!
719 * \internal
720 * \brief Fetch a user's ACLs from a source document and apply them to a target
721 *
722 * Unpack the given user's ACLs from the \c PCMK_XE_ACLS element in the source
723 * document to the \c acls list in the target document. Then set the target
724 * document's \c pcmk__xf_acl_enabled flag and apply the unpacked ACLs.
725 *
726 * \param[in] source XML document whose ACL definitions to use
727 * \param[in,out] target XML document to apply ACLs to
728 * \param[in] user User whose ACLs to apply
729 */
730 void
731 pcmk__enable_acls(xmlDoc *source, xmlDoc *target, const char *user)
732 {
733 if (target == NULL) {
734 return;
735 }
736 pcmk__unpack_acls(source, target->_private, user);
737 pcmk__xml_doc_set_flags(target, pcmk__xf_acl_enabled);
738 pcmk__apply_acls(target);
739 }
740
741 /*!
742 * \internal
743 * \brief Check whether a flag group allows the requested ACL access
744 *
745 * At most one ACL mode flag should be set in the flag group, but this function
746 * defines an order of precedence if multiple flags are set.
747 *
748 * \param[in] flags Group of <tt>enum pcmk__xml_flags</tt>
749 * \param[in] mode Requested access type (one of \c pcmk__xf_acl_read,
750 * \c pcmk__xf_acl_write, or \c pcmk__xf_acl_create)
751 *
752 * \return \c true if \p flags allows the access type in \p mode, or \c false
753 * otherwise
754 *
755 * \note \c pcmk__xf_acl_deny is an allowed value for \p mode, but it would make
756 * no sense. This function always returns \c false if \c pcmk__xf_acl_deny
757 * is set in \p flags.
758 */
759 static bool
760 is_mode_allowed(uint32_t flags, enum pcmk__xml_flags mode)
761 {
762 if (pcmk__is_set(flags, pcmk__xf_acl_deny)) {
763 // All access is denied
764 return false;
765 }
766
767 switch (mode) {
768 case pcmk__xf_acl_read:
769 // Write access provides read access
770 return pcmk__any_flags_set(flags,
771 pcmk__xf_acl_read|pcmk__xf_acl_write);
772
773 case pcmk__xf_acl_write:
774 return pcmk__is_set(flags, pcmk__xf_acl_write);
775
776 case pcmk__xf_acl_create:
777 /* Write access provides create access.
778 *
779 * @TODO Why does the \c pcmk__xf_created flag provide create
780 * access? This was introduced by commit e2ed85fe.
781 */
782 return pcmk__any_flags_set(flags,
783 pcmk__xf_acl_write|pcmk__xf_created);
784
785 default:
786 // Invalid mode
787 return false;
788 }
789 }
790
791 /*!
792 * \internal
793 * \brief Check whether an XML attribute's name is not \c PCMK_XA_ID
794 *
795 * \param[in] attr Attribute to check
796 * \param[in] user_data Ignored
797 *
798 * \return \c true if the attribute's name is not \c PCMK_XA_ID, or \c false
799 * otherwise
800 *
801 * \note This is compatible with \c pcmk__xe_remove_matching_attrs().
802 */
803 static bool
804 attr_is_not_id(xmlAttr *attr, void *user_data)
805 {
806 return !pcmk__str_eq((const char *) attr->name, PCMK_XA_ID, pcmk__str_none);
807 }
808
809 /*!
810 * \internal
811 * \brief Rid XML tree of all unreadable nodes and node properties
812 *
813 * \param[in,out] xml Root XML node to be purged of attributes
814 *
815 * \return true if this node or any of its children are readable
816 * if false is returned, xml will be freed
817 *
818 * \note This function is recursive
819 */
820 static bool
821 purge_xml_attributes(xmlNode *xml)
822 {
823 xmlNode *child = NULL;
824 bool readable_children = false;
825 xml_node_private_t *nodepriv = xml->_private;
826
827 if (is_mode_allowed(nodepriv->flags, pcmk__xf_acl_read)) {
828 pcmk__trace("%s[@" PCMK_XA_ID "=%s] is readable", xml->name,
829 pcmk__xe_id(xml));
830 return true;
831 }
832
833 pcmk__xe_remove_matching_attrs(xml, true, attr_is_not_id, NULL);
834
835 child = pcmk__xe_first_child(xml, NULL, NULL, NULL);
836 while (child != NULL) {
837 xmlNode *tmp = child;
838
839 child = pcmk__xe_next(child, NULL);
840
841 if (purge_xml_attributes(tmp)) {
842 readable_children = true;
843 }
844 }
845
846 if (!readable_children) {
847 // Nothing readable under here, so purge completely
848 pcmk__xml_free(xml);
849 }
850 return readable_children;
851 }
852
853 /*!
854 * \brief Copy ACL-allowed portions of specified XML
855 *
856 * \param[in] user Username whose ACLs should be used
857 * \param[in] acl_source XML containing ACLs
858 * \param[in] xml XML to be copied
859 * \param[out] result Copy of XML portions readable via ACLs
860 *
861 * \return \c true if \p acl_source and \p xml are non-<tt>NULL</tt> and ACLs
862 * are required for \p user, or \c false otherwise
863 *
864 * \note If this returns true, caller should use \p result rather than \p xml
865 */
866 bool
867 xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
868 xmlNode **result)
869 {
870 xmlNode *target = NULL;
871 xml_doc_private_t *docpriv = NULL;
872
873 *result = NULL;
|
(1) Event path: |
Condition "acl_source == NULL", taking false branch. |
|
(2) Event path: |
Condition "acl_source->doc == NULL", taking false branch. |
|
(3) Event path: |
Condition "xml == NULL", taking false branch. |
|
(4) Event path: |
Condition "!pcmk_acl_required(user)", taking false branch. |
874 if ((acl_source == NULL) || (acl_source->doc == NULL) || (xml == NULL)
875 || !pcmk_acl_required(user)) {
876
877 return false;
878 }
879
880 target = pcmk__xml_copy(NULL, xml);
881 docpriv = target->doc->_private;
882
883 pcmk__enable_acls(acl_source->doc, target->doc, user);
884
|
(5) Event path: |
Switch case default. |
|
(6) Event path: |
Condition "trace_cs == NULL", taking true branch. |
|
(7) Event path: |
Condition "crm_is_callsite_active(trace_cs, _level, 0)", taking false branch. |
|
(8) Event path: |
Breaking from switch. |
885 pcmk__trace("Filtering XML copy using user '%s' ACLs", user);
886
|
(9) Event path: |
Condition "iter != NULL", taking true branch. |
|
(12) Event path: |
Condition "iter != NULL", taking false branch. |
887 for (const GList *iter = docpriv->acls; iter != NULL; iter = iter->next) {
888 const xml_acl_t *acl = iter->data;
889 xmlXPathObject *xpath_obj = NULL;
890 int num_results = 0;
891
|
(10) Event path: |
Condition "acl->mode != pcmk__xf_acl_deny", taking true branch. |
892 if ((acl->mode != pcmk__xf_acl_deny) || (acl->xpath == NULL)) {
|
(11) Event path: |
Continuing loop. |
893 continue;
894 }
895
896 xpath_obj = pcmk__xpath_search(target->doc, acl->xpath);
897 num_results = pcmk__xpath_num_results(xpath_obj);
898
899 for (int i = 0; i < num_results; i++) {
900 xmlNode *match = pcmk__xpath_result(xpath_obj, i);
901
902 if (match == NULL) {
903 continue;
904 }
905
906 // @COMPAT See COMPAT comment in pcmk__apply_acls()
907 match = pcmk__xpath_match_element(match);
908 if (match == NULL) {
909 continue;
910 }
911
912 if (!purge_xml_attributes(match) && (match == target)) {
913 pcmk__trace("ACLs deny user '%s' access to entire XML document",
914 user);
915 xmlXPathFreeObject(xpath_obj);
916 return true;
917 }
918 }
919 pcmk__trace("ACLs deny user '%s' access to %s (%d match%s)", user,
920 acl->xpath, num_results,
921 pcmk__plural_alt(num_results, "", "es"));
922 xmlXPathFreeObject(xpath_obj);
923 }
924
|
(13) Event path: |
Condition "!purge_xml_attributes(target)", taking false branch. |
925 if (!purge_xml_attributes(target)) {
926 pcmk__trace("ACLs deny user '%s' access to entire XML document", user);
927 return true;
928 }
929
|
(14) Event path: |
Condition "docpriv->acls == NULL", taking false branch. |
930 if (docpriv->acls == NULL) {
931 pcmk__trace("User '%s' without ACLs denied access to entire XML "
932 "document", user);
933 pcmk__xml_free(target);
934 return true;
935 }
936
|
CID (unavailable; MK=f2ba8458b48d1447e509345f81a048a5) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS): |
|
(15) Event assign_union_field: |
The union field "in" of "_pp" is written. |
|
(16) Event inconsistent_union_field_access: |
In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in". |
937 g_clear_pointer(&docpriv->acls, pcmk__free_acls);
938 *result = target;
939 return true;
940 }
941
942 /*!
943 * \internal
944 * \brief Check whether creation of an XML element is implicitly allowed
945 *
946 * Check whether XML is a "scaffolding" element whose creation is implicitly
947 * allowed regardless of ACLs (that is, it is not in the ACL section and has
948 * no attributes other than \c PCMK_XA_ID).
949 *
950 * \param[in] xml XML element to check
951 *
952 * \return \c true if XML element is implicitly allowed, or \c false otherwise
953 */
954 static bool
955 implicitly_allowed(xmlNode *xml)
956 {
957 for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr != NULL;
958 attr = attr->next) {
959
960 if (attr_is_not_id(attr, NULL)) {
961 return false;
962 }
963 }
964
965 /* Creation is not implicitly allowed for a descendant of PCMK_XE_ACLS, but
966 * it may be for PCMK_XE_ACLS itself. Start checking at xml->parent and walk
967 * up the tree.
968 */
969 for (xml = xml->parent; xml != NULL; xml = xml->parent) {
970 if (pcmk__xe_is(xml, PCMK_XE_ACLS)) {
971 return false;
972 }
973 }
974
975 return true;
976 }
977
978 #define display_id(xml) pcmk__s(pcmk__xe_id(xml), "<unset>")
979
980 /*!
981 * \internal
982 * \brief Drop XML nodes created in violation of ACLs
983 *
984 * Given an XML element, free all of its descendant nodes created in violation
985 * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
986 * that aren't in the ACL section and don't have any attributes other than
987 * \c PCMK_XA_ID).
988 *
989 * \param[in,out] xml XML to check
990 * \param[in] check_top Whether to apply checks to argument itself
991 * (if true, xml might get freed)
992 *
993 * \note This function is recursive
994 */
995 void
996 pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
997 {
998 xml_node_private_t *nodepriv = xml->_private;
999
1000 if (pcmk__is_set(nodepriv->flags, pcmk__xf_created)) {
1001 if (implicitly_allowed(xml)) {
1002 pcmk__trace("Creation of <%s> scaffolding with "
1003 PCMK_XA_ID "=\"%s\" is implicitly allowed",
1004 xml->name, display_id(xml));
1005
1006 } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
1007 pcmk__trace("ACLs allow creation of <%s> with "
1008 PCMK_XA_ID "=\"%s\"",
1009 xml->name, display_id(xml));
1010
1011 } else if (check_top) {
1012 /* is_root=true should be impossible with check_top=true, but check
1013 * for sanity
1014 */
1015 bool is_root = (xmlDocGetRootElement(xml->doc) == xml);
1016 xml_doc_private_t *docpriv = xml->doc->_private;
1017
1018 pcmk__trace("ACLs disallow creation of %s<%s> with "
1019 PCMK_XA_ID "=\"%s\"",
1020 (is_root? "root element " : ""), xml->name,
1021 display_id(xml));
1022
1023 // pcmk__xml_free() checks ACLs if enabled, which would fail
1024 pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
1025 pcmk__xml_free(xml);
1026
1027 if (!is_root) {
1028 // If root, the document was freed. Otherwise re-enable ACLs.
1029 pcmk__set_xml_flags(docpriv, pcmk__xf_acl_enabled);
1030 }
1031 return;
1032
1033 } else {
1034 const bool is_root = (xml == xmlDocGetRootElement(xml->doc));
1035
1036 pcmk__notice("ACLs would disallow creation of %s<%s> with "
1037 PCMK_XA_ID "=\"%s\"",
1038 (is_root? "root element " : ""), xml->name,
1039 display_id(xml));
1040 }
1041 }
1042
1043 for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
1044 xmlNode *child = cIter;
1045 cIter = pcmk__xml_next(cIter); /* In case it is free'd */
1046 pcmk__apply_creation_acl(child, true);
1047 }
1048 }
1049
1050 /*!
1051 * \brief Check whether or not an XML node is ACL-denied
1052 *
1053 * \param[in] xml node to check
1054 *
1055 * \return true if XML node exists and is ACL-denied, false otherwise
1056 */
1057 bool
1058 xml_acl_denied(const xmlNode *xml)
1059 {
1060 if (xml && xml->doc && xml->doc->_private){
1061 xml_doc_private_t *docpriv = xml->doc->_private;
1062
1063 return pcmk__is_set(docpriv->flags, pcmk__xf_acl_denied);
1064 }
1065 return false;
1066 }
1067
1068 void
1069 xml_acl_disable(xmlNode *xml)
1070 {
1071 if ((xml != NULL)
1072 && pcmk__xml_doc_all_flags_set(xml->doc, pcmk__xf_acl_enabled)) {
1073
1074 xml_doc_private_t *docpriv = xml->doc->_private;
1075
1076 /* Catch anything that was created but shouldn't have been */
1077 pcmk__apply_acls(xml->doc);
1078 pcmk__apply_creation_acl(xml, false);
1079 pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
1080 }
1081 }
1082
1083 /*!
1084 * \internal
1085 * \brief Deny access to an XML tree's document based on ACLs
1086 *
1087 * \param[in,out] xml XML tree
1088 * \param[in] attr_name Name of attribute being accessed in \p xml (for
1089 * logging only)
1090 * \param[in] prefix Prefix describing ACL that denied access (for
1091 * logging only)
1092 * \param[in] user User accessing \p xml (for logging only)
1093 * \param[in] mode Access mode (for logging only)
1094 */
1095 #define check_acl_deny(xml, attr_name, prefix, user, mode) do { \
1096 xmlNode *tree = xml; \
1097 \
1098 pcmk__xml_doc_set_flags(tree->doc, pcmk__xf_acl_denied); \
1099 pcmk__if_tracing( \
1100 { \
1101 GString *xpath = pcmk__element_xpath(tree); \
1102 \
1103 if ((attr_name) != NULL) { \
1104 pcmk__g_strcat(xpath, "[@", attr_name, "]", NULL); \
1105 } \
1106 qb_log_from_external_source(__func__, __FILE__, \
1107 "%sACL denies user '%s' %s " \
1108 "access to %s", \
1109 LOG_TRACE, __LINE__, 0 , \
1110 prefix, user, \
1111 acl_mode_text(mode), \
1112 xpath->str); \
1113 g_string_free(xpath, TRUE); \
1114 }, \
1115 {} \
1116 ); \
1117 } while (0)
1118
1119 bool
1120 pcmk__check_acl(xmlNode *xml, const char *attr_name, enum pcmk__xml_flags mode)
1121 {
1122 xml_doc_private_t *docpriv = NULL;
1123
1124 pcmk__assert((xml != NULL) && (xml->doc->_private != NULL));
1125
1126 if (!pcmk__xml_doc_all_flags_set(xml->doc,
1127 pcmk__xf_tracking|pcmk__xf_acl_enabled)) {
1128 return true;
1129 }
1130
1131 docpriv = xml->doc->_private;
1132 if (docpriv->acls == NULL) {
1133 check_acl_deny(xml, attr_name, "Lack of ", docpriv->acl_user, mode);
1134 return false;
1135 }
1136
1137 /* Walk the tree upwards looking for xml_acl_* flags
1138 * - Creating an attribute requires write permissions for the node
1139 * - Creating a child requires write permissions for the parent
1140 */
1141
1142 if (attr_name != NULL) {
1143 xmlAttr *attr = xmlHasProp(xml, (const xmlChar *) attr_name);
1144
1145 if ((attr != NULL) && (mode == pcmk__xf_acl_create)) {
1146 mode = pcmk__xf_acl_write;
1147 }
1148 }
1149
1150 for (const xmlNode *parent = xml;
1151 (parent != NULL) && (parent->_private != NULL);
1152 parent = parent->parent) {
1153
1154 const xml_node_private_t *nodepriv = parent->_private;
1155
1156 if (is_mode_allowed(nodepriv->flags, mode)) {
1157 return true;
1158 }
1159
1160 if (pcmk__is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
1161 const char *pfx = (parent != xml)? "Parent " : "";
1162
1163 check_acl_deny(xml, attr_name, pfx, docpriv->acl_user, mode);
1164 return false;
1165 }
1166 }
1167
1168 check_acl_deny(xml, attr_name, "Default ", docpriv->acl_user, mode);
1169 return false;
1170 }
1171
1172 /*!
1173 * \brief Check whether ACLs are required for a given user
1174 *
1175 * \param[in] User name to check
1176 *
1177 * \return true if the user requires ACLs, false otherwise
1178 */
1179 bool
1180 pcmk_acl_required(const char *user)
1181 {
1182 if (pcmk__str_empty(user)) {
1183 pcmk__trace("ACLs not required because no user set");
1184 return false;
1185
1186 } else if (pcmk__is_privileged(user)) {
1187 pcmk__trace("ACLs not required for privileged user %s", user);
1188 return false;
1189 }
1190 pcmk__trace("ACLs required for %s", user);
1191 return true;
1192 }
1193
1194 char *
1195 pcmk__uid2username(uid_t uid)
1196 {
1197 struct passwd *pwent = NULL;
1198
1199 errno = 0;
1200 pwent = getpwuid(uid);
1201
1202 if (pwent == NULL) {
1203 pcmk__err("Cannot get name from password database for user ID %lld: %s",
1204 (long long) uid,
1205 ((errno != 0)? strerror(errno) : "No matching entry found"));
1206 return NULL;
1207 }
1208
1209 return pcmk__str_copy(pwent->pw_name);
1210 }
1211
1212 /*!
1213 * \internal
1214 * \brief Set the ACL user field properly on an XML request
1215 *
1216 * Multiple user names are potentially involved in an XML request: the effective
1217 * user of the current process; the user name known from an IPC client
1218 * connection; and the user name obtained from the request itself, whether by
1219 * the current standard XML attribute name or an older legacy attribute name.
1220 * This function chooses the appropriate one that should be used for ACLs, sets
1221 * it in the request (using the standard attribute name, and the legacy name if
1222 * given), and returns it.
1223 *
1224 * \param[in,out] request XML request to update
1225 * \param[in] field Alternate name for ACL user name XML attribute
1226 * \param[in] peer_user User name as known from IPC connection
1227 *
1228 * \return ACL user name actually used
1229 */
1230 const char *
1231 pcmk__update_acl_user(xmlNode *request, const char *field,
1232 const char *peer_user)
1233 {
1234 static const char *effective_user = NULL;
1235 const char *requested_user = NULL;
1236 const char *user = NULL;
1237
1238 if (effective_user == NULL) {
1239 effective_user = pcmk__uid2username(geteuid());
1240 if (effective_user == NULL) {
1241 effective_user = pcmk__str_copy("#unprivileged");
1242 pcmk__err("Unable to determine effective user, assuming "
1243 "unprivileged for ACLs");
1244 }
1245 }
1246
1247 requested_user = pcmk__xe_get(request, PCMK__XA_ACL_TARGET);
1248 if (requested_user == NULL) {
1249 /* Currently, different XML attribute names are used for the ACL user in
1250 * different contexts (PCMK__XA_ATTR_USER, PCMK__XA_CIB_USER, etc.).
1251 * The caller may specify that name as the field argument.
1252 *
1253 * @TODO Standardize on PCMK__XA_ACL_TARGET and eventually drop the
1254 * others once rolling upgrades from versions older than that are no
1255 * longer supported.
1256 */
1257 requested_user = pcmk__xe_get(request, field);
1258 }
1259
1260 if (!pcmk__is_privileged(effective_user)) {
1261 /* We're not running as a privileged user, set or overwrite any existing
1262 * value for PCMK__XA_ACL_TARGET
1263 */
1264 user = effective_user;
1265
1266 } else if (peer_user == NULL && requested_user == NULL) {
1267 /* No user known or requested, use 'effective_user' and make sure one is
1268 * set for the request
1269 */
1270 user = effective_user;
1271
1272 } else if (peer_user == NULL) {
1273 /* No user known, trusting 'requested_user' */
1274 user = requested_user;
1275
1276 } else if (!pcmk__is_privileged(peer_user)) {
1277 /* The peer is not a privileged user, set or overwrite any existing
1278 * value for PCMK__XA_ACL_TARGET
1279 */
1280 user = peer_user;
1281
1282 } else if (requested_user == NULL) {
1283 /* Even if we're privileged, make sure there is always a value set */
1284 user = peer_user;
1285
1286 } else {
1287 /* Legal delegation to 'requested_user' */
1288 user = requested_user;
1289 }
1290
1291 // This requires pointer comparison, not string comparison
1292 if (user != pcmk__xe_get(request, PCMK__XA_ACL_TARGET)) {
1293 pcmk__xe_set(request, PCMK__XA_ACL_TARGET, user);
1294 }
1295
1296 if ((field != NULL) && (user != pcmk__xe_get(request, field))) {
1297 pcmk__xe_set(request, field, user);
1298 }
1299
1300 return requested_user;
1301 }
1302
1303 // Deprecated functions kept only for backward API compatibility
1304 // LCOV_EXCL_START
1305
1306 #include <crm/common/acl_compat.h>
1307 #include <crm/common/xml_compat.h>
1308
1309 bool
1310 xml_acl_enabled(const xmlNode *xml)
1311 {
1312 if (xml && xml->doc && xml->doc->_private){
1313 xml_doc_private_t *docpriv = xml->doc->_private;
1314
1315 return pcmk__is_set(docpriv->flags, pcmk__xf_acl_enabled);
1316 }
1317 return false;
1318 }
1319
1320 // LCOV_EXCL_STOP
1321 // End deprecated API
1322