From adc0a6e89484869393ca8b11db90569d4a21ef60 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Fri, 25 Aug 2023 13:56:21 +1200 Subject: [PATCH 1/4] python: Fix invalid escape sequences Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett (cherry picked from commit b068592dd0dccce634cb17b66f0659ba60523908) --- python/samba/gp/gp_cert_auto_enroll_ext.py | 6 +- python/samba/graph.py | 2 +- python/samba/tests/gpo.py | 66 +++++++++++----------- python/samba/tests/samba_tool/gpo.py | 2 +- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py index 08d1a7348cd..2122cc439f4 100644 --- a/python/samba/gp/gp_cert_auto_enroll_ext.py +++ b/python/samba/gp/gp_cert_auto_enroll_ext.py @@ -337,7 +337,7 @@ def cert_enroll(ca, ldb, trust_dir, private_dir, auth='Kerberos'): class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier): def __str__(self): - return 'Cryptography\AutoEnrollment' + return r'Cryptography\AutoEnrollment' def unapply(self, guid, attribute, value): ca_cn = base64.b64decode(attribute) @@ -389,7 +389,7 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier): for gpo in changed_gpo_list: if gpo.file_sys_path: - section = 'Software\Policies\Microsoft\Cryptography\AutoEnrollment' + section = r'Software\Policies\Microsoft\Cryptography\AutoEnrollment' pol_file = 'MACHINE/Registry.pol' path = os.path.join(gpo.file_sys_path, pol_file) pol_conf = self.parse(path) @@ -509,7 +509,7 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier): def rsop(self, gpo): output = {} pol_file = 'MACHINE/Registry.pol' - section = 'Software\Policies\Microsoft\Cryptography\AutoEnrollment' + section = r'Software\Policies\Microsoft\Cryptography\AutoEnrollment' if gpo.file_sys_path: path = os.path.join(gpo.file_sys_path, pol_file) pol_conf = self.parse(path) diff --git a/python/samba/graph.py b/python/samba/graph.py index 537dc661fb3..4c4a07f47ae 100644 --- a/python/samba/graph.py +++ b/python/samba/graph.py @@ -192,7 +192,7 @@ def compile_graph_key(key_items, nodes_above=None, elisions=None, short = short[1:] long = long[1:] elision_str += ('\nelision%d[shape=plaintext; style=solid; ' - 'label="\“%s” means “%s”\\r"]\n' + 'label="\\“%s” means “%s”\\r"]\n' % ((i, short, long))) above_lines = [] diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py index 580f3568de8..c4c6b3d95bb 100644 --- a/python/samba/tests/gpo.py +++ b/python/samba/tests/gpo.py @@ -123,7 +123,7 @@ dspath = 'CN=Policies,CN=System,' + base_dn gpt_data = '[General]\nVersion=%d' gnome_test_reg_pol = \ -b""" +br""" @@ -260,7 +260,7 @@ b""" """ auto_enroll_reg_pol = \ -b""" +br""" @@ -282,7 +282,7 @@ b""" """ auto_enroll_unchecked_reg_pol = \ -b""" +br""" @@ -304,7 +304,7 @@ b""" """ advanced_enroll_reg_pol = \ -b""" +br""" @@ -338,122 +338,122 @@ b""" 0 - Software\Policies\Microsoft\Cryptography\PolicyServers\\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 + Software\Policies\Microsoft\Cryptography\PolicyServers\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 URL LDAP: - Software\Policies\Microsoft\Cryptography\PolicyServers\\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 + Software\Policies\Microsoft\Cryptography\PolicyServers\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 PolicyID %s - Software\Policies\Microsoft\Cryptography\PolicyServers\\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 + Software\Policies\Microsoft\Cryptography\PolicyServers\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 FriendlyName Example - Software\Policies\Microsoft\Cryptography\PolicyServers\\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 + Software\Policies\Microsoft\Cryptography\PolicyServers\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 Flags 16 - Software\Policies\Microsoft\Cryptography\PolicyServers\\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 + Software\Policies\Microsoft\Cryptography\PolicyServers\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 AuthFlags 2 - Software\Policies\Microsoft\Cryptography\PolicyServers\\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 + Software\Policies\Microsoft\Cryptography\PolicyServers\37c9dc30f207f27f61a2f7c3aed598a6e2920b54 Cost 2147483645 - Software\Policies\Microsoft\Cryptography\PolicyServers\\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe + Software\Policies\Microsoft\Cryptography\PolicyServers\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe URL https://example2.com/ADPolicyProvider_CEP_Certificate/service.svc/CEP - Software\Policies\Microsoft\Cryptography\PolicyServers\\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe + Software\Policies\Microsoft\Cryptography\PolicyServers\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe PolicyID %s - Software\Policies\Microsoft\Cryptography\PolicyServers\\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe + Software\Policies\Microsoft\Cryptography\PolicyServers\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe FriendlyName Example2 - Software\Policies\Microsoft\Cryptography\PolicyServers\\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe + Software\Policies\Microsoft\Cryptography\PolicyServers\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe Flags 16 - Software\Policies\Microsoft\Cryptography\PolicyServers\\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe + Software\Policies\Microsoft\Cryptography\PolicyServers\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe AuthFlags 8 - Software\Policies\Microsoft\Cryptography\PolicyServers\\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe + Software\Policies\Microsoft\Cryptography\PolicyServers\144bdbb8e4717c26e408f3c9a0cb8d6cfacbcbbe Cost 10 - Software\Policies\Microsoft\Cryptography\PolicyServers\\20d46e856e9b9746c0b1265c328f126a7b3283a9 + Software\Policies\Microsoft\Cryptography\PolicyServers\20d46e856e9b9746c0b1265c328f126a7b3283a9 URL https://example0.com/ADPolicyProvider_CEP_Kerberos/service.svc/CEP - Software\Policies\Microsoft\Cryptography\PolicyServers\\20d46e856e9b9746c0b1265c328f126a7b3283a9 + Software\Policies\Microsoft\Cryptography\PolicyServers\20d46e856e9b9746c0b1265c328f126a7b3283a9 PolicyID %s - Software\Policies\Microsoft\Cryptography\PolicyServers\\20d46e856e9b9746c0b1265c328f126a7b3283a9 + Software\Policies\Microsoft\Cryptography\PolicyServers\20d46e856e9b9746c0b1265c328f126a7b3283a9 FriendlyName Example0 - Software\Policies\Microsoft\Cryptography\PolicyServers\\20d46e856e9b9746c0b1265c328f126a7b3283a9 + Software\Policies\Microsoft\Cryptography\PolicyServers\20d46e856e9b9746c0b1265c328f126a7b3283a9 Flags 16 - Software\Policies\Microsoft\Cryptography\PolicyServers\\20d46e856e9b9746c0b1265c328f126a7b3283a9 + Software\Policies\Microsoft\Cryptography\PolicyServers\20d46e856e9b9746c0b1265c328f126a7b3283a9 AuthFlags 2 - Software\Policies\Microsoft\Cryptography\PolicyServers\\20d46e856e9b9746c0b1265c328f126a7b3283a9 + Software\Policies\Microsoft\Cryptography\PolicyServers\20d46e856e9b9746c0b1265c328f126a7b3283a9 Cost 1 - Software\Policies\Microsoft\Cryptography\PolicyServers\\855b5246433a48402ac4f5c3427566df26ccc9ac + Software\Policies\Microsoft\Cryptography\PolicyServers\855b5246433a48402ac4f5c3427566df26ccc9ac URL https://example1.com/ADPolicyProvider_CEP_Kerberos/service.svc/CEP - Software\Policies\Microsoft\Cryptography\PolicyServers\\855b5246433a48402ac4f5c3427566df26ccc9ac + Software\Policies\Microsoft\Cryptography\PolicyServers\855b5246433a48402ac4f5c3427566df26ccc9ac PolicyID %s - Software\Policies\Microsoft\Cryptography\PolicyServers\\855b5246433a48402ac4f5c3427566df26ccc9ac + Software\Policies\Microsoft\Cryptography\PolicyServers\855b5246433a48402ac4f5c3427566df26ccc9ac FriendlyName Example1 - Software\Policies\Microsoft\Cryptography\PolicyServers\\855b5246433a48402ac4f5c3427566df26ccc9ac + Software\Policies\Microsoft\Cryptography\PolicyServers\855b5246433a48402ac4f5c3427566df26ccc9ac Flags 16 - Software\Policies\Microsoft\Cryptography\PolicyServers\\855b5246433a48402ac4f5c3427566df26ccc9ac + Software\Policies\Microsoft\Cryptography\PolicyServers\855b5246433a48402ac4f5c3427566df26ccc9ac AuthFlags 2 - Software\Policies\Microsoft\Cryptography\PolicyServers\\855b5246433a48402ac4f5c3427566df26ccc9ac + Software\Policies\Microsoft\Cryptography\PolicyServers\855b5246433a48402ac4f5c3427566df26ccc9ac Cost 1 @@ -2116,7 +2116,7 @@ firefox_json_expected = \ """ chromium_reg_pol = \ -b""" +br""" @@ -3012,12 +3012,12 @@ b""" Software\Policies\Google\Chrome RestrictSigninToPattern - .*@example\\.com + .*@example\.com Software\Policies\Google\Chrome RoamingProfileLocation - ${roaming_app_data}\\chrome-profile + ${roaming_app_data}\chrome-profile Software\Policies\Google\Chrome @@ -3267,7 +3267,7 @@ b""" Software\Policies\Google\Chrome\AlternativeBrowserParameters 5 - %HOME%\\browser_profile + %HOME%\browser_profile Software\Policies\Google\Chrome\AudioCaptureAllowedUrls @@ -4973,7 +4973,7 @@ b""" """ firewalld_reg_pol = \ -b""" +br""" diff --git a/python/samba/tests/samba_tool/gpo.py b/python/samba/tests/samba_tool/gpo.py index e49944c204d..654f254de7d 100644 --- a/python/samba/tests/samba_tool/gpo.py +++ b/python/samba/tests/samba_tool/gpo.py @@ -1806,7 +1806,7 @@ class GpoCmdTestCase(SambaToolCmdTest): 'The test cse was not enabled') self.assertIn('UserPolicy : False', out, 'The test cse should not have User policy enabled') - cse_ext = re.findall('^UniqueGUID\s+:\s+(.*)', out) + cse_ext = re.findall(r'^UniqueGUID\s+:\s+(.*)', out) self.assertEquals(len(cse_ext), 1, 'The test cse GUID was not found') cse_ext = cse_ext[0] -- 2.43.0 From bab1800ce308cf25f9bb12cb7fdb7ecccff4453c Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Mon, 8 Jan 2024 18:05:08 +0200 Subject: [PATCH 2/4] gpo: Test certificate policy without NDES As of 8231eaf856b, the NDES feature is no longer required on Windows, as cert auto-enroll can use the certificate from the LDAP request. However, 157335ee93e changed the implementation to convert the LDAP certificate to base64 due to it failing to cleanly convert to a string. Because of insufficient test coverage I missed handling the part where NDES is disabled or not reachable and the LDAP certificate was imported. The call to load_der_x509_certificate now fails with an error because it expects binary data, yet it receives a base64 encoded string. This adds a test to confirm the issue. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15557 Signed-off-by: Gabriel Nagy Reviewed-by: David Mulder Reviewed-by: Andreas Schneider (cherry picked from commit 0d1ff69936f18ea729fc11fbbb1569a833302572) --- python/samba/tests/gpo.py | 126 ++++++++++++++++++++++++++++++++++++-- selftest/knownfail.d/gpo | 1 + 2 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 selftest/knownfail.d/gpo diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py index c4c6b3d95bb..a6a33ea4ba1 100644 --- a/python/samba/tests/gpo.py +++ b/python/samba/tests/gpo.py @@ -102,17 +102,21 @@ def dummy_certificate(): # Dummy requests structure for Certificate Auto Enrollment class dummy_requests(object): - @staticmethod - def get(url=None, params=None): + class exceptions(object): + ConnectionError = Exception + + def __init__(self, want_exception=False): + self.want_exception = want_exception + + def get(self, url=None, params=None): + if self.want_exception: + raise self.exceptions.ConnectionError + dummy = requests.Response() dummy._content = dummy_certificate() dummy.headers = {'Content-Type': 'application/x-x509-ca-cert'} return dummy - class exceptions(object): - ConnectionError = Exception -cae.requests = dummy_requests - realm = os.environ.get('REALM') policies = realm + '/POLICIES' realm = realm.lower() @@ -6764,6 +6768,114 @@ class GPOTests(tests.TestCase): # Unstage the Registry.pol file unstage_file(reg_pol) + def test_gp_cert_auto_enroll_ext_without_ndes(self): + local_path = self.lp.cache_path('gpo_cache') + guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}' + reg_pol = os.path.join(local_path, policies, guid, + 'MACHINE/REGISTRY.POL') + cache_dir = self.lp.get('cache directory') + store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb')) + + machine_creds = Credentials() + machine_creds.guess(self.lp) + machine_creds.set_machine_account() + + # Initialize the group policy extension + cae.requests = dummy_requests(want_exception=True) + ext = cae.gp_cert_auto_enroll_ext(self.lp, machine_creds, + machine_creds.get_username(), store) + + gpos = get_gpo_list(self.server, machine_creds, self.lp, + machine_creds.get_username()) + + # Stage the Registry.pol file with test data + parser = GPPolParser() + parser.load_xml(etree.fromstring(auto_enroll_reg_pol.strip())) + ret = stage_file(reg_pol, ndr_pack(parser.pol_file)) + self.assertTrue(ret, 'Could not create the target %s' % reg_pol) + + # Write the dummy CA entry, Enrollment Services, and Templates Entries + admin_creds = Credentials() + admin_creds.set_username(os.environ.get('DC_USERNAME')) + admin_creds.set_password(os.environ.get('DC_PASSWORD')) + admin_creds.set_realm(os.environ.get('REALM')) + hostname = get_dc_hostname(machine_creds, self.lp) + url = 'ldap://%s' % hostname + ldb = Ldb(url=url, session_info=system_session(), + lp=self.lp, credentials=admin_creds) + # Write the dummy CA + confdn = 'CN=Public Key Services,CN=Services,CN=Configuration,%s' % base_dn + ca_cn = '%s-CA' % hostname.replace('.', '-') + certa_dn = 'CN=%s,CN=Certification Authorities,%s' % (ca_cn, confdn) + ldb.add({'dn': certa_dn, + 'objectClass': 'certificationAuthority', + 'authorityRevocationList': ['XXX'], + 'cACertificate': dummy_certificate(), + 'certificateRevocationList': ['XXX'], + }) + # Write the dummy pKIEnrollmentService + enroll_dn = 'CN=%s,CN=Enrollment Services,%s' % (ca_cn, confdn) + ldb.add({'dn': enroll_dn, + 'objectClass': 'pKIEnrollmentService', + 'cACertificate': dummy_certificate(), + 'certificateTemplates': ['Machine'], + 'dNSHostName': hostname, + }) + # Write the dummy pKICertificateTemplate + template_dn = 'CN=Machine,CN=Certificate Templates,%s' % confdn + ldb.add({'dn': template_dn, + 'objectClass': 'pKICertificateTemplate', + }) + + with TemporaryDirectory() as dname: + try: + ext.process_group_policy([], gpos, dname, dname) + except Exception as e: + self.fail(str(e)) + + ca_crt = os.path.join(dname, '%s.crt' % ca_cn) + self.assertTrue(os.path.exists(ca_crt), + 'Root CA certificate was not requested') + machine_crt = os.path.join(dname, '%s.Machine.crt' % ca_cn) + self.assertTrue(os.path.exists(machine_crt), + 'Machine certificate was not requested') + machine_key = os.path.join(dname, '%s.Machine.key' % ca_cn) + self.assertTrue(os.path.exists(machine_key), + 'Machine key was not generated') + + # Verify RSOP does not fail + ext.rsop([g for g in gpos if g.name == guid][0]) + + # Check that a call to gpupdate --rsop also succeeds + ret = rsop(self.lp) + self.assertEqual(ret, 0, 'gpupdate --rsop failed!') + + # Remove policy + gp_db = store.get_gplog(machine_creds.get_username()) + del_gpos = get_deleted_gpos_list(gp_db, []) + ext.process_group_policy(del_gpos, [], dname) + self.assertFalse(os.path.exists(ca_crt), + 'Root CA certificate was not removed') + self.assertFalse(os.path.exists(machine_crt), + 'Machine certificate was not removed') + self.assertFalse(os.path.exists(machine_key), + 'Machine key was not removed') + out, _ = Popen(['getcert', 'list-cas'], stdout=PIPE).communicate() + self.assertNotIn(get_bytes(ca_cn), out, 'CA was not removed') + out, _ = Popen(['getcert', 'list'], stdout=PIPE).communicate() + self.assertNotIn(b'Machine', out, + 'Machine certificate not removed') + self.assertNotIn(b'Workstation', out, + 'Workstation certificate not removed') + + # Remove the dummy CA, pKIEnrollmentService, and pKICertificateTemplate + ldb.delete(certa_dn) + ldb.delete(enroll_dn) + ldb.delete(template_dn) + + # Unstage the Registry.pol file + unstage_file(reg_pol) + def test_gp_cert_auto_enroll_ext(self): local_path = self.lp.cache_path('gpo_cache') guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}' @@ -6777,6 +6889,7 @@ class GPOTests(tests.TestCase): machine_creds.set_machine_account() # Initialize the group policy extension + cae.requests = dummy_requests() ext = cae.gp_cert_auto_enroll_ext(self.lp, machine_creds, machine_creds.get_username(), store) @@ -7241,6 +7354,7 @@ class GPOTests(tests.TestCase): machine_creds.set_machine_account() # Initialize the group policy extension + cae.requests = dummy_requests() ext = cae.gp_cert_auto_enroll_ext(self.lp, machine_creds, machine_creds.get_username(), store) diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo new file mode 100644 index 00000000000..f1e590bc7d8 --- /dev/null +++ b/selftest/knownfail.d/gpo @@ -0,0 +1 @@ +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext_without_ndes -- 2.43.0 From 2d2d792b03838d7c5e64e7c162b42a0aa84ca92a Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Thu, 18 Jan 2024 20:23:24 +0200 Subject: [PATCH 3/4] gpo: Decode base64 root cert before importing The reasoning behind this is described in the previous commit message, but essentially this should either be wrapped in certificate blocks and imported as PEM, or converted back to binary and imported as DER. I've opted for the latter since it's how it used to work before it regressed in 157335ee93e. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15557 Signed-off-by: Gabriel Nagy Reviewed-by: David Mulder Reviewed-by: Andreas Schneider (cherry picked from commit 3f3ddfa699a33c2c8a59f7fb9ee044bb2a6e0e06) --- python/samba/gp/gp_cert_auto_enroll_ext.py | 5 +++-- selftest/knownfail.d/gpo | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 selftest/knownfail.d/gpo diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py index 2122cc439f4..b3ecdc5bf5e 100644 --- a/python/samba/gp/gp_cert_auto_enroll_ext.py +++ b/python/samba/gp/gp_cert_auto_enroll_ext.py @@ -217,10 +217,11 @@ def getca(ca, url, trust_dir): ' installed or not configured.') if 'cACertificate' in ca: log.warn('Installing the server certificate only.') + der_certificate = base64.b64decode(ca['cACertificate']) try: - cert = load_der_x509_certificate(ca['cACertificate']) + cert = load_der_x509_certificate(der_certificate) except TypeError: - cert = load_der_x509_certificate(ca['cACertificate'], + cert = load_der_x509_certificate(der_certificate, default_backend()) cert_data = cert.public_bytes(Encoding.PEM) with open(root_cert, 'wb') as w: diff --git a/selftest/knownfail.d/gpo b/selftest/knownfail.d/gpo deleted file mode 100644 index f1e590bc7d8..00000000000 --- a/selftest/knownfail.d/gpo +++ /dev/null @@ -1 +0,0 @@ -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext_without_ndes -- 2.43.0 From 09c5b376008a03f2dc9e6dd926f10c5810242a32 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 19 Jan 2024 11:36:19 +0200 Subject: [PATCH 4/4] gpo: Do not get templates list on first run This is a visual fix and has no impact on functionality apart from cleaner log messages. The point of this is to get the list of supported templates in order to compute a diff between the current applied templates and the updated list, so we are able to unapply and reapply the policy in case there are differences. However this code path is executed on first applies as well, at which point the root CA is not yet set up. This causes the `get_supported_templates` call to fail, which is not a hard failure but still pollutes the logs. In this case it's safe to avoid executing the command as the policy will be applied regardless. Signed-off-by: Gabriel Nagy Reviewed-by: David Mulder Reviewed-by: Andreas Schneider Autobuild-User(master): Andreas Schneider Autobuild-Date(master): Mon Jan 22 16:48:57 UTC 2024 on atb-devel-224 (cherry picked from commit 8579340fc540633c13c017d896034904a8dbd55c) --- python/samba/gp/gp_cert_auto_enroll_ext.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/samba/gp/gp_cert_auto_enroll_ext.py b/python/samba/gp/gp_cert_auto_enroll_ext.py index b3ecdc5bf5e..df3b472f5a9 100644 --- a/python/samba/gp/gp_cert_auto_enroll_ext.py +++ b/python/samba/gp/gp_cert_auto_enroll_ext.py @@ -359,7 +359,8 @@ class gp_cert_auto_enroll_ext(gp_pol_ext, gp_applier): # If the policy has changed, unapply, then apply new policy old_val = self.cache_get_attribute_value(guid, attribute) old_data = json.loads(old_val) if old_val is not None else {} - templates = ['%s.%s' % (ca['name'], t.decode()) for t in get_supported_templates(ca['hostname'])] + templates = ['%s.%s' % (ca['name'], t.decode()) for t in get_supported_templates(ca['hostname'])] \ + if old_val is not None else [] new_data = { 'templates': templates, **ca } if changed(new_data, old_data) or self.cache_get_apply_state() == GPOSTATE.ENFORCE: self.unapply(guid, attribute, old_val) -- 2.43.0