Merge remote-tracking branch 'saltstack/3006.x' into merge-forward

This commit is contained in:
Daniel A. Wozniak 2023-12-17 14:58:34 -07:00
commit 7018b14a7c
2 changed files with 86 additions and 70 deletions

View file

@ -11,6 +11,7 @@ This module allows you to manage assistive access on macOS minions with 10.9+
import hashlib
import logging
import sqlite3
import time
import salt.utils.platform
import salt.utils.stringutils
@ -36,7 +37,7 @@ def __virtual__():
return __virtualname__
def install(app_id, enable=True):
def install(app_id, enable=True, tries=3, wait=10):
"""
Install a bundle ID or command as being allowed to use
assistive access.
@ -47,6 +48,12 @@ def install(app_id, enable=True):
enabled
Sets enabled or disabled status. Default is ``True``.
tries
How many times to try and write to a read-only tcc. Default is ``True``.
wait
Number of seconds to wait between tries. Default is ``10``.
CLI Example:
.. code-block:: bash
@ -54,13 +61,23 @@ def install(app_id, enable=True):
salt '*' assistive.install /usr/bin/osascript
salt '*' assistive.install com.smileonmymac.textexpander
"""
with TccDB() as db:
try:
return db.install(app_id, enable=enable)
except sqlite3.Error as exc:
raise CommandExecutionError(
"Error installing app({}): {}".format(app_id, exc)
)
num_tries = 1
while True:
with TccDB() as db:
try:
return db.install(app_id, enable=enable)
except sqlite3.Error as exc:
if "attempt to write a readonly database" not in str(exc):
raise CommandExecutionError(
"Error installing app({}): {}".format(app_id, exc)
)
elif num_tries < tries:
time.sleep(wait)
num_tries += 1
else:
raise CommandExecutionError(
"Error installing app({}): {}".format(app_id, exc)
)
def installed(app_id):
@ -175,22 +192,21 @@ class TccDB:
def _check_table_digest(self):
# This logic comes from https://github.com/jacobsalmela/tccutil which is
# Licensed under GPL-2.0
with self.connection as conn:
cursor = conn.execute(
"SELECT sql FROM sqlite_master WHERE name='access' and type='table'"
)
for row in cursor.fetchall():
digest = hashlib.sha1(row["sql"].encode()).hexdigest()[:10]
if digest in ("ecc443615f", "80a4bb6912"):
# Mojave and Catalina
self.ge_mojave_and_catalina = True
elif digest in ("3d1c2a0e97", "cef70648de"):
# BigSur and later
self.ge_bigsur_and_later = True
else:
raise CommandExecutionError(
"TCC Database structure unknown for digest '{}'".format(digest)
)
cursor = self.connection.execute(
"SELECT sql FROM sqlite_master WHERE name='access' and type='table'"
)
for row in cursor.fetchall():
digest = hashlib.sha1(row["sql"].encode()).hexdigest()[:10]
if digest in ("ecc443615f", "80a4bb6912"):
# Mojave and Catalina
self.ge_mojave_and_catalina = True
elif digest in ("3d1c2a0e97", "cef70648de"):
# BigSur and later
self.ge_bigsur_and_later = True
else:
raise CommandExecutionError(
"TCC Database structure unknown for digest '{}'".format(digest)
)
def _get_client_type(self, app_id):
if app_id[0] == "/":
@ -200,14 +216,13 @@ class TccDB:
return 0
def installed(self, app_id):
with self.connection as conn:
cursor = conn.execute(
"SELECT * from access WHERE client=? and service='kTCCServiceAccessibility'",
(app_id,),
)
for row in cursor.fetchall():
if row:
return True
cursor = self.connection.execute(
"SELECT * from access WHERE client=? and service='kTCCServiceAccessibility'",
(app_id,),
)
for row in cursor.fetchall():
if row:
return True
return False
def install(self, app_id, enable=True):
@ -234,9 +249,8 @@ class TccDB:
# indirect_object_identifier
# ),
# FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
with self.connection as conn:
conn.execute(
"""
self.connection.execute(
"""
INSERT or REPLACE INTO access VALUES (
'kTCCServiceAccessibility',
?,
@ -253,8 +267,9 @@ class TccDB:
0
)
""",
(app_id, client_type, auth_value),
)
(app_id, client_type, auth_value),
)
self.connection.commit()
elif self.ge_mojave_and_catalina:
# CREATE TABLE IF NOT EXISTS "access" (
# service TEXT NOT NULL,
@ -276,9 +291,8 @@ class TccDB:
# indirect_object_identifier
# ),
# FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
with self.connection as conn:
conn.execute(
"""
self.connection.execute(
"""
INSERT or REPLACE INTO access VALUES(
'kTCCServiceAccessibility',
?,
@ -294,8 +308,9 @@ class TccDB:
0
)
""",
(app_id, client_type, auth_value),
)
(app_id, client_type, auth_value),
)
self.connection.commit()
return True
def enabled(self, app_id):
@ -303,14 +318,13 @@ class TccDB:
column = "auth_value"
elif self.ge_mojave_and_catalina:
column = "allowed"
with self.connection as conn:
cursor = conn.execute(
"SELECT * from access WHERE client=? and service='kTCCServiceAccessibility'",
(app_id,),
)
for row in cursor.fetchall():
if row[column]:
return True
cursor = self.connection.execute(
"SELECT * from access WHERE client=? and service='kTCCServiceAccessibility'",
(app_id,),
)
for row in cursor.fetchall():
if row[column]:
return True
return False
def enable(self, app_id):
@ -320,13 +334,13 @@ class TccDB:
column = "auth_value"
elif self.ge_mojave_and_catalina:
column = "allowed"
with self.connection as conn:
conn.execute(
"UPDATE access SET {} = ? WHERE client=? AND service IS 'kTCCServiceAccessibility'".format(
column
),
(1, app_id),
)
self.connection.execute(
"UPDATE access SET {} = ? WHERE client=? AND service IS 'kTCCServiceAccessibility'".format(
column
),
(1, app_id),
)
self.connection.commit()
return True
def disable(self, app_id):
@ -336,23 +350,23 @@ class TccDB:
column = "auth_value"
elif self.ge_mojave_and_catalina:
column = "allowed"
with self.connection as conn:
conn.execute(
"UPDATE access SET {} = ? WHERE client=? AND service IS 'kTCCServiceAccessibility'".format(
column
),
(0, app_id),
)
self.connection.execute(
"UPDATE access SET {} = ? WHERE client=? AND service IS 'kTCCServiceAccessibility'".format(
column
),
(0, app_id),
)
self.connection.commit()
return True
def remove(self, app_id):
if not self.installed(app_id):
return False
with self.connection as conn:
conn.execute(
"DELETE from access where client IS ? AND service IS 'kTCCServiceAccessibility'",
(app_id,),
)
self.connection.execute(
"DELETE from access where client IS ? AND service IS 'kTCCServiceAccessibility'",
(app_id,),
)
self.connection.commit()
return True
def __enter__(self):

View file

@ -64,6 +64,7 @@ def test_pillar_timeout(salt_master_factory):
"auto_accept": True,
"worker_threads": 2,
"peer": True,
"minion_data_cache": False,
}
minion_overrides = {
"auth_timeout": 20,
@ -77,7 +78,7 @@ def test_pillar_timeout(salt_master_factory):
- name: example
- changes: True
- result: True
- comment: "Nothing has actually been changed"
- comment: "Nothing has actually been changed {{ pillar['foo'] }}"
"""
master = salt_master_factory.salt_master_daemon(
"pillar-timeout-master",
@ -103,6 +104,7 @@ def test_pillar_timeout(salt_master_factory):
sls_tempfile = master.state_tree.base.temp_file(f"{sls_name}.sls", sls_contents)
with master.started(), minion1.started(), minion2.started(), minion3.started(), minion4.started(), sls_tempfile:
proc = cli.run("state.sls", sls_name, minion_tgt="*")
print(proc)
# At least one minion should have a Pillar timeout
assert proc.returncode == 1
minion_timed_out = False