pan-os-python 1.12.4__tar.gz → 1.12.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pan-os-python
3
- Version: 1.12.4
3
+ Version: 1.12.6
4
4
  Summary: Framework for interacting with Palo Alto Networks devices via API
5
5
  Home-page: https://github.com/PaloAltoNetworks/pan-os-python
6
6
  License: ISC
@@ -26,13 +26,13 @@ Documentation available at https://pan-os-python.readthedocs.io
26
26
 
27
27
  __author__ = "Palo Alto Networks"
28
28
  __email__ = "devrel@paloaltonetworks.com"
29
- __version__ = "1.12.4"
29
+ __version__ = "1.12.6"
30
30
 
31
31
 
32
32
  import logging
33
+ import re
33
34
  import sys
34
35
  import xml.etree.ElementTree as ET
35
- from distutils.version import LooseVersion # Used by PanOSVersion class
36
36
 
37
37
  # Warn if running on end-of-life python
38
38
  if sys.version_info < (3, 6):
@@ -127,7 +127,28 @@ pan.DEBUG2 = pan.DEBUG1 - 1
127
127
  pan.DEBUG3 = pan.DEBUG2 - 1
128
128
 
129
129
 
130
- class PanOSVersion(LooseVersion):
130
+ class _LooseVersion:
131
+ """Minimal LooseVersion replacement; distutils was removed in Python 3.12."""
132
+
133
+ _component_re = re.compile(r"(\d+|[a-z]+|\.)")
134
+
135
+ def __init__(self, vstring=None):
136
+ if vstring:
137
+ self.parse(vstring)
138
+
139
+ def parse(self, vstring):
140
+ self.vstring = vstring
141
+ parts = [x for x in self._component_re.split(vstring) if x and x != "."]
142
+ self.version = [int(x) if x.isdigit() else x for x in parts]
143
+
144
+ def __str__(self):
145
+ return self.vstring
146
+
147
+ def __repr__(self):
148
+ return "LooseVersion ('%s')" % str(self)
149
+
150
+
151
+ class PanOSVersion(_LooseVersion):
131
152
  """LooseVersion with convenience properties to access version components"""
132
153
 
133
154
  @property
@@ -29,6 +29,7 @@ import sys
29
29
  import time
30
30
  import xml.dom.minidom as minidom
31
31
  import xml.etree.ElementTree as ET
32
+ from xml.sax.saxutils import escape as xml_escape
32
33
 
33
34
  import pan.commit
34
35
  import pan.xapi
@@ -36,6 +37,31 @@ from pan.config import PanConfig
36
37
 
37
38
  import panos
38
39
  import panos.errors as err
40
+
41
+ # Defined before sub-module imports below: panos.userid imports _xpath_safe
42
+ # from this module, so the symbol must exist by the time `from panos import
43
+ # userid` triggers userid's module body.
44
+ SELF = "/%s"
45
+ ENTRY = "/entry[@name=%s]"
46
+ MEMBER = "/member[text()=%s]"
47
+
48
+
49
+ def _xpath_safe(val):
50
+ """Return val as an XPath 1.0 string literal, safe to inject into a predicate.
51
+
52
+ XPath 1.0 has no escape syntax for quotes inside string literals, so a value
53
+ containing quotes must be wrapped in the opposite quote, or split into a
54
+ concat() expression when both quote types are present.
55
+ """
56
+ val = "" if val is None else str(val)
57
+ if "'" not in val:
58
+ return "'" + val + "'"
59
+ if '"' not in val:
60
+ return '"' + val + '"'
61
+ parts = val.split("'")
62
+ return "concat('" + "', \"'\", '".join(parts) + "')"
63
+
64
+
39
65
  from panos import (
40
66
  chunk_instances_for_delete_similar,
41
67
  isstring,
@@ -48,9 +74,6 @@ from panos import (
48
74
  logger = panos.getlogger(__name__)
49
75
 
50
76
  Root = panos.enum("DEVICE", "VSYS", "MGTCONFIG", "PANORAMA", "PANORAMA_VSYS")
51
- SELF = "/%s"
52
- ENTRY = "/entry[@name='%s']"
53
- MEMBER = "/member[text()='%s']"
54
77
 
55
78
 
56
79
  # PanObject type
@@ -340,7 +363,7 @@ class PanObject(object):
340
363
  # xpath was asked for.
341
364
  addon = p.XPATH
342
365
  if p.SUFFIX is not None:
343
- addon += p.SUFFIX % (p.uid,)
366
+ addon += p.SUFFIX % (_xpath_safe(p.uid),)
344
367
  path.insert(0, addon)
345
368
  if p.__class__.__name__ == "Firewall" and p.parent is not None:
346
369
  if p.parent.__class__.__name__ == "DeviceGroup":
@@ -412,7 +435,7 @@ class PanObject(object):
412
435
  xpath = "/config/shared"
413
436
  else:
414
437
  xpath = "/config/devices/entry[@name='localhost.localdomain']"
415
- xpath += "/{0}/entry[@name='{1}']".format(label, vsys or "vsys1")
438
+ xpath += "/{0}/entry[@name={1}]".format(label, _xpath_safe(vsys or "vsys1"))
416
439
 
417
440
  return xpath
418
441
 
@@ -476,7 +499,7 @@ class PanObject(object):
476
499
  regex,
477
500
  matchedvar.path
478
501
  + "/"
479
- + "entry[@name='%s']" % entry_value[0],
502
+ + "entry[@name=%s]" % _xpath_safe(entry_value[0]),
480
503
  section,
481
504
  )
482
505
  entryvar = matchedvar
@@ -860,7 +883,9 @@ class PanObject(object):
860
883
  entry_value = panos.string_or_list(getattr(self, matchedvar.variable))
861
884
  varpath = re.sub(
862
885
  regex,
863
- matchedvar.path + "/" + "entry[@name='%s']" % entry_value[0],
886
+ matchedvar.path
887
+ + "/"
888
+ + "entry[@name=%s]" % _xpath_safe(entry_value[0]),
864
889
  varpath,
865
890
  )
866
891
  else:
@@ -1448,7 +1473,9 @@ class PanObject(object):
1448
1473
  replacement = replacement[0]
1449
1474
  path = re.sub(
1450
1475
  regex,
1451
- matchedvar.path + "/" + "entry[@name='%s']" % replacement,
1476
+ matchedvar.path
1477
+ + "/"
1478
+ + "entry[@name=%s]" % _xpath_safe(replacement),
1452
1479
  path,
1453
1480
  )
1454
1481
  else:
@@ -1983,10 +2010,10 @@ class PanObject(object):
1983
2010
  prefix = ""
1984
2011
  xpath = self.xpath_nosuffix()
1985
2012
  if self.SUFFIX == ENTRY:
1986
- joiner = "@name='{0}'"
2013
+ joiner = "@name={0}"
1987
2014
  prefix = "entry"
1988
2015
  elif self.SUFFIX == MEMBER:
1989
- joiner = "text()='{0}'"
2016
+ joiner = "text()={0}"
1990
2017
  prefix = "member"
1991
2018
 
1992
2019
  # After some testing, PAN-OS seems to be able to handle a DELETE API call
@@ -1999,7 +2026,7 @@ class PanObject(object):
1999
2026
  "{0}/{1}[{2}]".format(
2000
2027
  xpath,
2001
2028
  prefix,
2002
- " or ".join(joiner.format(x.uid) for x in chunk),
2029
+ " or ".join(joiner.format(_xpath_safe(x.uid)) for x in chunk),
2003
2030
  ),
2004
2031
  retry_on_peer=self.HA_SYNC,
2005
2032
  )
@@ -2036,7 +2063,9 @@ class PanObject(object):
2036
2063
  """Iterates over a vsys_dict, deleting the import for all instances."""
2037
2064
  for vsys_spec in vsys_dict.values():
2038
2065
  for objs in vsys_spec.values():
2039
- members = " or ".join("text()='{0}'".format(x.uid) for x in objs)
2066
+ members = " or ".join(
2067
+ "text()={0}".format(_xpath_safe(x.uid)) for x in objs
2068
+ )
2040
2069
  xpath = "{0}/member[{1}]".format(objs[0].xpath_import_base(), members)
2041
2070
  # API complains if you try to do this in one delete statement,
2042
2071
  # so do one delete per vsys per path, just like when we set the
@@ -2431,7 +2460,7 @@ class VersionedPanObject(PanObject):
2431
2460
 
2432
2461
  _DEFAULT_NAME = None
2433
2462
  _TEMPLATE_DEVICE_XPATH = "/config/devices/entry[@name='localhost.localdomain']"
2434
- _TEMPLATE_VSYS_XPATH = _TEMPLATE_DEVICE_XPATH + "/vsys/entry[@name='{vsys}']"
2463
+ _TEMPLATE_VSYS_XPATH = _TEMPLATE_DEVICE_XPATH + "/vsys/entry[@name={vsys}]"
2435
2464
  _TEMPLATE_MGTCONFIG_XPATH = "/config/mgt-config"
2436
2465
 
2437
2466
  def __init__(self, *args, **kwargs):
@@ -2638,7 +2667,7 @@ class VersionedPanObject(PanObject):
2638
2667
  if ap.startswith("entry "):
2639
2668
  junk, var_to_use = ap.split()
2640
2669
  sol_value = panos.string_or_list(settings[var_to_use])[0]
2641
- finder = "entry[@name='{0}']".format(sol_value)
2670
+ finder = "entry[@name={0}]".format(_xpath_safe(sol_value))
2642
2671
  tag = "entry"
2643
2672
  attribs["name"] = sol_value
2644
2673
  elif ap == "entry[@name='localhost.localdomain']":
@@ -2725,8 +2754,8 @@ class VersionedPanObject(PanObject):
2725
2754
  p = None
2726
2755
  if token.startswith("entry "):
2727
2756
  junk, var_to_use = token.split()
2728
- p = "entry[name='{0}']".format(
2729
- *(x for x in self._value_as_list(settings[var_to_use]))
2757
+ p = "entry[name={0}]".format(
2758
+ *(_xpath_safe(x) for x in self._value_as_list(settings[var_to_use]))
2730
2759
  )
2731
2760
  else:
2732
2761
  p = None
@@ -2835,7 +2864,7 @@ class VersionedPanObject(PanObject):
2835
2864
  """Returns the version specific xpath of this object."""
2836
2865
  panos_version = self.retrieve_panos_version()
2837
2866
  val = self._xpaths._get_versioned_value(panos_version, self.parent)
2838
- return val.format(vsys=self.vsys or "vsys1")
2867
+ return val.format(vsys=_xpath_safe(self.vsys or "vsys1"))
2839
2868
 
2840
2869
 
2841
2870
  class VersionedParamPath(VersioningSupport):
@@ -3198,7 +3227,7 @@ class ParamPath(object):
3198
3227
  return
3199
3228
  settings[entry_var] = ans.attrib["name"]
3200
3229
  sol_val = panos.string_or_list(settings[entry_var])[0]
3201
- path_str = "entry[@name='{0}']".format(sol_val)
3230
+ path_str = "entry[@name={0}]".format(_xpath_safe(sol_val))
3202
3231
  else:
3203
3232
  # Standard path part
3204
3233
  try:
@@ -3386,7 +3415,7 @@ class VsysOperations(VersionedPanObject):
3386
3415
 
3387
3416
  if vsys != "shared" and vsys is not None and self.XPATH_IMPORT is not None:
3388
3417
  xpath = self.xpath_import_base(vsys)
3389
- element = "<member>{0}</member>".format(self.uid)
3418
+ element = "<member>{0}</member>".format(xml_escape(self.uid))
3390
3419
  device = self.nearest_pandevice()
3391
3420
  device.active().xapi.set(xpath, element, retry_on_peer=True)
3392
3421
 
@@ -3420,8 +3449,8 @@ class VsysOperations(VersionedPanObject):
3420
3449
  p = p.parent
3421
3450
 
3422
3451
  if vsys != "shared" and vsys is not None and self.XPATH_IMPORT is not None:
3423
- xpath = "{0}/member[text()='{1}']".format(
3424
- self.xpath_import_base(vsys), self.uid
3452
+ xpath = "{0}/member[text()={1}]".format(
3453
+ self.xpath_import_base(vsys), _xpath_safe(self.uid)
3425
3454
  )
3426
3455
  device = self.nearest_pandevice()
3427
3456
  device.active().xapi.delete(xpath, retry_on_peer=True)
@@ -3830,9 +3859,11 @@ class PanDevice(PanObject):
3830
3859
  def method(self, *args, **kwargs):
3831
3860
  retry_on_peer = kwargs.pop(
3832
3861
  "retry_on_peer",
3833
- True
3834
- if super_method_name not in ("keygen", "op", "ad_hoc", "export")
3835
- else False,
3862
+ (
3863
+ True
3864
+ if super_method_name not in ("keygen", "op", "ad_hoc", "export")
3865
+ else False
3866
+ ),
3836
3867
  )
3837
3868
  apply_on_peer = kwargs.pop("apply_on_peer", False)
3838
3869
  ha_peer = self.pan_device.ha_peer
@@ -27,6 +27,7 @@ import panos.errors as err
27
27
  from panos import device, getlogger, yesno
28
28
  from panos.base import ENTRY, PanDevice, Root
29
29
  from panos.base import VarPath as Var
30
+ from panos.base import _xpath_safe
30
31
 
31
32
  logger = getlogger(__name__)
32
33
 
@@ -333,7 +334,7 @@ class Firewall(PanDevice):
333
334
  devices_xpath = self.devicegroup().xpath() + self.XPATH
334
335
  devices_xml = panorama.xapi.get(devices_xpath)
335
336
  dg_vsys = devices_xml.findall(
336
- "result/devices/entry[@name='%s']/vsys/entry" % self.serial
337
+ "result/devices/entry[@name=%s]/vsys/entry" % _xpath_safe(self.serial)
337
338
  )
338
339
  if dg_vsys:
339
340
  if len(dg_vsys) == 1:
@@ -344,7 +345,7 @@ class Firewall(PanDevice):
344
345
  # It's not the only vsys, just delete the vsys
345
346
  panorama.set_config_changed()
346
347
  panorama.xapi.delete(
347
- self.xpath() + "/vsys/entry[@name='%s']" % self.vsys
348
+ self.xpath() + "/vsys/entry[@name=%s]" % _xpath_safe(self.vsys)
348
349
  )
349
350
  else:
350
351
  # This is a firewall under a panorama
@@ -392,7 +393,7 @@ class Firewall(PanDevice):
392
393
  )
393
394
  # Add system settings to firewall instances
394
395
  for fw in firewall_instances:
395
- entry = xml.find("entry[@name='%s']" % fw.serial)
396
+ entry = xml.find("entry[@name=%s]" % _xpath_safe(fw.serial))
396
397
  system = fw.find_or_create(None, device.SystemSettings)
397
398
  system.hostname = entry.findtext("hostname")
398
399
  system.ip_address = entry.findtext("ip-address")
@@ -591,7 +592,6 @@ class FirewallCommit(object):
591
592
  self.exclude_device_and_network,
592
593
  self.exclude_shared_objects,
593
594
  self.exclude_policy_and_objects,
594
- self.force,
595
595
  ]
596
596
 
597
597
  return any(x for x in pp_list)
@@ -622,11 +622,9 @@ class FirewallCommit(object):
622
622
  ET.SubElement(partial, "shared-object").text = "excluded"
623
623
  if self.exclude_policy_and_objects:
624
624
  ET.SubElement(partial, "policy-and-objects").text = "excluded"
625
+ fe.append(partial)
625
626
 
626
- if self.force:
627
- fe = ET.SubElement(root, "force")
628
- fe.append(partial)
629
- else:
630
- root.append(partial)
627
+ if self.force:
628
+ fe = ET.SubElement(root, "force")
631
629
 
632
630
  return root
@@ -26,7 +26,12 @@ import panos.errors as err
26
26
  from panos import device, getlogger, string_or_list
27
27
  from panos.base import ENTRY, MEMBER, PanObject, Root
28
28
  from panos.base import VarPath as Var
29
- from panos.base import VersionedPanObject, VersionedParamPath, VsysOperations
29
+ from panos.base import (
30
+ VersionedPanObject,
31
+ VersionedParamPath,
32
+ VsysOperations,
33
+ _xpath_safe,
34
+ )
30
35
 
31
36
  logger = getlogger(__name__)
32
37
 
@@ -741,7 +746,7 @@ class Subinterface(Interface):
741
746
  if self._BASE_INTERFACE_NAME in path:
742
747
  base = self.uid.split(".")[0]
743
748
  path = path.replace(
744
- self._BASE_INTERFACE_NAME, "entry[@name='{0}']".format(base)
749
+ self._BASE_INTERFACE_NAME, "entry[@name={0}]".format(_xpath_safe(base))
745
750
  )
746
751
 
747
752
  return path
@@ -28,7 +28,7 @@ import panos.errors as err
28
28
  from panos import base, firewall, getlogger, policies, yesno
29
29
  from panos.base import ENTRY, MEMBER, OpState, PanObject, Root
30
30
  from panos.base import VarPath as Var
31
- from panos.base import VersionedPanObject, VersionedParamPath
31
+ from panos.base import VersionedPanObject, VersionedParamPath, _xpath_safe
32
32
 
33
33
  logger = getlogger(__name__)
34
34
 
@@ -643,7 +643,7 @@ class Panorama(base.PanDevice):
643
643
  serial = str(device)
644
644
  if serial is None:
645
645
  continue
646
- entry = devices_xml.find("entry[@name='%s']" % serial)
646
+ entry = devices_xml.find("entry[@name=%s]" % _xpath_safe(serial))
647
647
  if entry is None:
648
648
  if only_connected:
649
649
  raise err.PanNotConnectedOnPanorama(
@@ -661,7 +661,10 @@ class Panorama(base.PanDevice):
661
661
  except AttributeError:
662
662
  continue
663
663
  # Create entry if needed
664
- if filtered_devices_xml.find("entry[@name='%s']" % serial) is None:
664
+ if (
665
+ filtered_devices_xml.find("entry[@name=%s]" % _xpath_safe(serial))
666
+ is None
667
+ ):
665
668
  entry_copy = deepcopy(entry)
666
669
  # If looking for specific vsys, erase all vsys in filtered entry
667
670
  if vsys != "shared" and vsys is not None:
@@ -670,7 +673,7 @@ class Panorama(base.PanDevice):
670
673
  filtered_devices_xml.append(entry_copy)
671
674
  # Get specific vsys
672
675
  if vsys != "shared" and vsys is not None:
673
- vsys_entry = entry.find("vsys/entry[@name='%s']" % vsys)
676
+ vsys_entry = entry.find("vsys/entry[@name=%s]" % _xpath_safe(vsys))
674
677
  if vsys_entry is None:
675
678
  raise err.PanNotAttachedOnPanorama(
676
679
  "Can't find device with serial %s and"
@@ -678,7 +681,7 @@ class Panorama(base.PanDevice):
678
681
  % (serial, vsys, self.id)
679
682
  )
680
683
  vsys_section = filtered_devices_xml.find(
681
- "entry[@name='%s']/vsys" % serial
684
+ "entry[@name=%s]/vsys" % _xpath_safe(serial)
682
685
  )
683
686
  vsys_section.append(vsys_entry)
684
687
  devices_xml = filtered_devices_xml
@@ -733,7 +736,8 @@ class Panorama(base.PanDevice):
733
736
  continue
734
737
  for fw_entry in dg_entry.find("devices"):
735
738
  fw_entry_op = devicegroup_opxml.find(
736
- "entry/devices/entry[@name='%s']" % fw_entry.get("name")
739
+ "entry/devices/entry[@name=%s]"
740
+ % _xpath_safe(fw_entry.get("name"))
737
741
  )
738
742
  if fw_entry_op is not None:
739
743
  panos.xml_combine(fw_entry, fw_entry_op)
@@ -748,7 +752,7 @@ class Panorama(base.PanDevice):
748
752
  dg_serials = [
749
753
  entry.get("name")
750
754
  for entry in devicegroup_configxml.findall(
751
- "entry[@name='%s']/devices/entry" % dg.name
755
+ "entry[@name=%s]/devices/entry" % _xpath_safe(dg.name)
752
756
  )
753
757
  ]
754
758
  # Find firewall with each serial
@@ -759,13 +763,14 @@ class Panorama(base.PanDevice):
759
763
  all_dg_vsys = [
760
764
  entry.get("name")
761
765
  for entry in devicegroup_configxml.findall(
762
- "entry[@name='%s']/devices/entry[@name='%s']/vsys/entry"
763
- % (dg.name, dg_serial)
766
+ "entry[@name=%s]/devices/entry[@name=%s]/vsys/entry"
767
+ % (_xpath_safe(dg.name), _xpath_safe(dg_serial))
764
768
  )
765
769
  ]
766
770
  # Collect the firewall serial entry to get current status information
767
771
  fw_entry = devicegroup_configxml.find(
768
- "entry[@name='%s']/devices/entry[@name='%s']" % (dg.name, dg_serial)
772
+ "entry[@name=%s]/devices/entry[@name=%s]"
773
+ % (_xpath_safe(dg.name), _xpath_safe(dg_serial))
769
774
  )
770
775
  if not all_dg_vsys:
771
776
  # This is a single-context firewall, assume vsys1
@@ -807,7 +812,8 @@ class Panorama(base.PanDevice):
807
812
  shared_policy_status = fw_entry.findtext("shared-policy-status")
808
813
  if shared_policy_status is None:
809
814
  shared_policy_status = fw_entry.findtext(
810
- "vsys/entry[@name='%s']/shared-policy-status" % dg_vsys
815
+ "vsys/entry[@name=%s]/shared-policy-status"
816
+ % _xpath_safe(dg_vsys)
811
817
  )
812
818
  fw.state.set_shared_policy_synced(shared_policy_status)
813
819
 
@@ -990,7 +996,6 @@ class PanoramaCommit(object):
990
996
  self.log_collector_groups,
991
997
  self.exclude_device_and_network,
992
998
  self.exclude_shared_objects,
993
- self.force,
994
999
  ]
995
1000
 
996
1001
  return any(x for x in pp_list)
@@ -1031,12 +1036,10 @@ class PanoramaCommit(object):
1031
1036
  ET.SubElement(partial, "device-and-network").text = "excluded"
1032
1037
  if self.exclude_shared_objects:
1033
1038
  ET.SubElement(partial, "shared-object").text = "excluded"
1039
+ root.append(partial)
1034
1040
 
1035
- if self.force:
1036
- fe = ET.SubElement(root, "force")
1037
- fe.append(partial)
1038
- else:
1039
- root.append(partial)
1041
+ if self.force:
1042
+ fe = ET.SubElement(root, "force")
1040
1043
 
1041
1044
  return root
1042
1045
 
@@ -21,6 +21,7 @@ from pan.xapi import PanXapiError
21
21
 
22
22
  import panos.errors as err
23
23
  from panos import getlogger, objects
24
+ from panos.base import _xpath_safe
24
25
  from panos.updater import PanOSVersion
25
26
 
26
27
  logger = getlogger(__name__)
@@ -43,7 +44,7 @@ class Predefined(object):
43
44
 
44
45
  # xpath
45
46
  XPATH = "/config/predefined"
46
- SINGLE_ENTRY_XPATH = "/entry[@name='{0}']"
47
+ SINGLE_ENTRY_XPATH = "/entry[@name={0}]"
47
48
  ALL_ENTRIES_XPATH = "/entry"
48
49
  CHILDTYPES = (
49
50
  "objects.ApplicationContainer",
@@ -76,7 +77,7 @@ class Predefined(object):
76
77
  x.parent = self
77
78
  xpath = x.xpath_nosuffix()
78
79
  if name is not None:
79
- xpath += self.SINGLE_ENTRY_XPATH.format(name)
80
+ xpath += self.SINGLE_ENTRY_XPATH.format(_xpath_safe(name))
80
81
  else:
81
82
  xpath += self.ALL_ENTRIES_XPATH
82
83
 
@@ -19,11 +19,13 @@
19
19
 
20
20
  import xml.etree.ElementTree as ET
21
21
  from copy import deepcopy
22
+ from xml.sax.saxutils import escape, quoteattr
22
23
 
23
24
  from pan.xapi import PanXapiError
24
25
 
25
26
  import panos.errors as err
26
27
  from panos import getlogger, string_or_list, string_or_list_or_none
28
+ from panos.base import _xpath_safe
27
29
  from panos.updater import PanOSVersion
28
30
 
29
31
  logger = getlogger(__name__)
@@ -244,7 +246,7 @@ class UserId(object):
244
246
  return
245
247
  tags = [self.prefix + t for t in tags]
246
248
  for c_ip in ip:
247
- tagelement = register.find("./entry[@ip='%s']/tag" % c_ip)
249
+ tagelement = register.find("./entry[@ip=%s]/tag" % _xpath_safe(c_ip))
248
250
  if tagelement is None:
249
251
  entry = ET.SubElement(register, "entry", {"ip": c_ip})
250
252
  tagelement = ET.SubElement(entry, "tag")
@@ -275,7 +277,7 @@ class UserId(object):
275
277
  return
276
278
  tags = [self.prefix + t for t in tags]
277
279
  for c_ip in ip:
278
- tagelement = unregister.find("./entry[@ip='%s']/tag" % c_ip)
280
+ tagelement = unregister.find("./entry[@ip=%s]/tag" % _xpath_safe(c_ip))
279
281
  if tagelement is None:
280
282
  entry = ET.SubElement(unregister, "entry", {"ip": c_ip})
281
283
  tagelement = ET.SubElement(entry, "tag")
@@ -550,7 +552,7 @@ class UserId(object):
550
552
  "<show><user><group><list>",
551
553
  ]
552
554
  if style is not None:
553
- msg.append("<entry name='{0}'/>".format(style))
555
+ msg.append("<entry name={0}/>".format(quoteattr(style)))
554
556
  msg.append("</list></group></user></show>")
555
557
  cmd = "".join(msg)
556
558
  vsys = self.device.vsys or "vsys1"
@@ -594,7 +596,11 @@ class UserId(object):
594
596
  list
595
597
 
596
598
  """
597
- cmd = "<show><user><group><name>" + group + "</name></group></user></show>"
599
+ cmd = (
600
+ "<show><user><group><name>"
601
+ + escape(group)
602
+ + "</name></group></user></show>"
603
+ )
598
604
  vsys = self.device.vsys or "vsys1"
599
605
 
600
606
  resp = self.device.op(cmd, vsys=vsys, cmd_xml=False)
@@ -644,12 +650,12 @@ class UserId(object):
644
650
  if user is None:
645
651
  msg.append(
646
652
  "<all>"
647
- + "<limit>{0}</limit>".format(limit)
648
- + "<start-point>{0}</start-point>".format(start)
653
+ + "<limit>{0}</limit>".format(escape(str(limit)))
654
+ + "<start-point>{0}</start-point>".format(escape(str(start)))
649
655
  + "</all>"
650
656
  )
651
657
  else:
652
- msg.append("<user>{0}</user>".format(user))
658
+ msg.append("<user>{0}</user>".format(escape(user)))
653
659
  msg.append("</registered-user></object></show>")
654
660
 
655
661
  cmd = ET.fromstring("".join(msg))
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pan-os-python"
3
- version = "1.12.4"
3
+ version = "1.12.6"
4
4
  description = "Framework for interacting with Palo Alto Networks devices via API"
5
5
  authors = ["Palo Alto Networks <devrel@paloaltonetworks.com>"]
6
6
  license = "ISC"
File without changes
File without changes