summaryrefslogtreecommitdiffstats
path: root/python/samba/tests/token_factory.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /python/samba/tests/token_factory.py
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python/samba/tests/token_factory.py')
-rw-r--r--python/samba/tests/token_factory.py256
1 files changed, 256 insertions, 0 deletions
diff --git a/python/samba/tests/token_factory.py b/python/samba/tests/token_factory.py
new file mode 100644
index 0000000..22f87f0
--- /dev/null
+++ b/python/samba/tests/token_factory.py
@@ -0,0 +1,256 @@
+# Unix SMB/CIFS implementation.
+# Copyright © Catalyst IT 2023
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""An API for creating arbitrary security tokens."""
+
+
+from samba.dcerpc import security
+
+
+CLAIM_VAL_TYPES = {
+ int: 0x0001,
+ 'uint': 0x0002,
+ str: 0x0003,
+ security.dom_sid: 0x0005,
+ bool: 0x0006,
+ bytes: 0x0010
+}
+
+
+def list_to_claim(k, v, case_sensitive=False):
+ if isinstance(v, security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1):
+ # make the name match
+ v.name = k
+ return v
+ if isinstance(v, (str, int)):
+ v = [v]
+ if not isinstance(v, list):
+ raise TypeError(f"expected list of claim values for '{k}', "
+ f"not {v!r} of type {type(v)}")
+
+ c = security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1()
+
+ if len(v) != 0:
+ t = type(v[0])
+ c.value_type = CLAIM_VAL_TYPES[t]
+ for val in v[1:]:
+ if type(val) != t:
+ raise TypeError(f"claim values for '{k}' "
+ "should all be the same type")
+ else:
+ # pick an arbitrary type
+ c.value_type = CLAIM_VAL_TYPES['uint']
+ c.name = k
+ c.values = v
+ c.value_count = len(v)
+ if case_sensitive:
+ c.flags |= security.CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE
+
+ # The claims made here will not have the
+ # CLAIM_SECURITY_ATTRIBUTE_UNIQUE_AND_SORTED flag set, which makes
+ # them like resource attribute claims rather than real wire
+ # claims. It shouldn't matter much, as they will just be sorted
+ # and checked as if they were resource attribute claims.
+ return c
+
+
+def _normalise_claims(args):
+ if isinstance(args, security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1):
+ return [args]
+
+ if args is None or len(args) == 0:
+ return []
+
+ if isinstance(args, list):
+ for x in args:
+ if not isinstance(x, security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1):
+ raise TypeError(f"list should be of claims, not '{type(x)}'")
+ return args
+
+ claims_out = []
+
+ if isinstance(args, dict):
+ # the key is the name and the value is a list of claim values
+ for k, v in args.items():
+ c = list_to_claim(k, v)
+ claims_out.append(c)
+
+ return claims_out
+
+
+def str_to_sid(s):
+ lut = {
+ # These are a subset of two letter aliases that don't need a
+ # domain SID or other magic. (c.f. sid_strings test).
+ 'AA': security.SID_BUILTIN_ACCESS_CONTROL_ASSISTANCE_OPS, # S-1-5-32-579
+ 'AC': security.SID_SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE, # S-1-15-2-1
+ 'AN': security.SID_NT_ANONYMOUS, # S-1-5-7
+ 'AO': security.SID_BUILTIN_ACCOUNT_OPERATORS, # S-1-5-32-548
+ 'AS': security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, # S-1-18-1
+ 'AU': security.SID_NT_AUTHENTICATED_USERS, # S-1-5-11
+ 'BA': security.SID_BUILTIN_ADMINISTRATORS, # S-1-5-32-544
+ 'BG': security.SID_BUILTIN_GUESTS, # S-1-5-32-546
+ 'BO': security.SID_BUILTIN_BACKUP_OPERATORS, # S-1-5-32-551
+ 'BU': security.SID_BUILTIN_USERS, # S-1-5-32-545
+ 'CD': security.SID_BUILTIN_CERT_SERV_DCOM_ACCESS, # S-1-5-32-574
+ 'CG': security.SID_CREATOR_GROUP, # S-1-3-1
+ 'CO': security.SID_CREATOR_OWNER, # S-1-3-0
+ 'CY': security.SID_BUILTIN_CRYPTO_OPERATORS, # S-1-5-32-569
+ 'ED': security.SID_NT_ENTERPRISE_DCS, # S-1-5-9
+ 'ER': security.SID_BUILTIN_EVENT_LOG_READERS, # S-1-5-32-573
+ 'ES': security.SID_BUILTIN_RDS_ENDPOINT_SERVERS, # S-1-5-32-576
+ 'HA': security.SID_BUILTIN_HYPER_V_ADMINS, # S-1-5-32-578
+ 'HI': security.SID_SECURITY_MANDATORY_HIGH, # S-1-16-12288
+ 'IS': security.SID_BUILTIN_IUSERS, # S-1-5-32-568
+ 'IU': security.SID_NT_INTERACTIVE, # S-1-5-4
+ 'LS': security.SID_NT_LOCAL_SERVICE, # S-1-5-19
+ 'LU': security.SID_BUILTIN_PERFLOG_USERS, # S-1-5-32-559
+ 'LW': security.SID_SECURITY_MANDATORY_LOW, # S-1-16-4096
+ 'ME': security.SID_SECURITY_MANDATORY_MEDIUM, # S-1-16-8192
+ 'MP': security.SID_SECURITY_MANDATORY_MEDIUM_PLUS, # S-1-16-8448
+ 'MS': security.SID_BUILTIN_RDS_MANAGEMENT_SERVERS, # S-1-5-32-577
+ 'MU': security.SID_BUILTIN_PERFMON_USERS, # S-1-5-32-558
+ 'NO': security.SID_BUILTIN_NETWORK_CONF_OPERATORS, # S-1-5-32-556
+ 'NS': security.SID_NT_NETWORK_SERVICE, # S-1-5-20
+ 'NU': security.SID_NT_NETWORK, # S-1-5-2
+ 'OW': security.SID_OWNER_RIGHTS, # S-1-3-4
+ 'PO': security.SID_BUILTIN_PRINT_OPERATORS, # S-1-5-32-550
+ 'PS': security.SID_NT_SELF, # S-1-5-10
+ 'PU': security.SID_BUILTIN_POWER_USERS, # S-1-5-32-547
+ 'RA': security.SID_BUILTIN_RDS_REMOTE_ACCESS_SERVERS, # S-1-5-32-575
+ 'RC': security.SID_NT_RESTRICTED, # S-1-5-12
+ 'RD': security.SID_BUILTIN_REMOTE_DESKTOP_USERS, # S-1-5-32-555
+ 'RE': security.SID_BUILTIN_REPLICATOR, # S-1-5-32-552
+ 'RM': security.SID_BUILTIN_REMOTE_MANAGEMENT_USERS, # S-1-5-32-580
+ 'RU': security.SID_BUILTIN_PREW2K, # S-1-5-32-554
+ 'SI': security.SID_SECURITY_MANDATORY_SYSTEM, # S-1-16-16384
+ 'SO': security.SID_BUILTIN_SERVER_OPERATORS, # S-1-5-32-549
+ 'SS': security.SID_SERVICE_ASSERTED_IDENTITY, # S-1-18-2
+ 'SU': security.SID_NT_SERVICE, # S-1-5-6
+ 'SY': security.SID_NT_SYSTEM, # S-1-5-18
+ 'WD': security.SID_WORLD, # S-1-1-0
+ 'WR': security.SID_SECURITY_RESTRICTED_CODE, # S-1-5-33
+ }
+ if s in lut:
+ s = lut[s]
+ return security.dom_sid(s)
+
+
+def _normalise_sids(args):
+ if isinstance(args, security.dom_sid):
+ return [args]
+ if isinstance(args, str):
+ return [str_to_sid(args)]
+
+ if not isinstance(args, list):
+ raise TypeError("expected a SID, sid string, or list of SIDs, "
+ f"not'{type(args)}'")
+
+ sids_out = []
+ for s in args:
+ if isinstance(s, str):
+ s = str_to_sid(s)
+ elif not isinstance(s, security.dom_sid):
+ raise TypeError(f"expected a SID, not'{type(s)}'")
+ sids_out.append(s)
+
+ return sids_out
+
+
+def _normalise_mask(mask, mask_type):
+ if isinstance(mask, int):
+ return mask
+
+ if not isinstance(mask, list):
+ raise TypeError("expected int mask or list of flags")
+
+ if mask_type == 'privileges':
+ prefix = 'SEC_PRIV_'
+ tail = '_BIT'
+ elif mask_type == 'rights':
+ prefix = 'LSA_POLICY_MODE_'
+ tail = ''
+ else:
+ raise ValueError(f"unknown mask_type value: {mask_type}")
+
+ mask_out = 0
+
+ for x in mask:
+ if isinstance(x, str) and x.startswith(prefix):
+ if not x.endswith(tail):
+ # we don't want security.SEC_PRIV_SHUTDOWN (19),
+ # we want security.SEC_PRIV_SHUTDOWN_BIT (1 << 20)
+ # but you can write "SEC_PRIV_SHUTDOWN"
+ x += tail
+ x = getattr(security, x)
+ mask_out |= x
+
+ return mask_out
+
+
+def token(sids=None, **kwargs):
+ """Return a security token with the specified attributes.
+
+ The security.token API is annoying and fragile; here we wrap it in
+ something nicer.
+
+ In general the arguments can either be objects of the correct
+ type, or Python strings or structures that clearly convert to that
+ type. For example, there two are equivalent:
+
+ >>> t = token([security.dom_sid("S-1-2")])
+ >>> t = token(["S-1-2"])
+
+ To add claims and device SIDs you do something like this:
+
+ >>> t = token(["AA", WD"],
+ device_sids=["WD"],
+ user_claims={"Title": ["PM"],
+ "ClearanceLevel": [1]}
+ """
+
+ claims_kws = ['device_claims',
+ 'local_claims',
+ 'user_claims']
+
+ sid_kws = ['sids', 'device_sids']
+
+ mask_kws = ['privileges',
+ 'rights']
+
+ if sids is not None:
+ kwargs['sids'] = sids
+
+ norm_args = {}
+
+ for k, v in kwargs.items():
+ if k in claims_kws:
+ norm_args[k] = _normalise_claims(v)
+ elif k in mask_kws:
+ norm_args[k] = _normalise_mask(v, k)
+ elif k in sid_kws:
+ norm_args[k] = _normalise_sids(v)
+ else:
+ raise TypeError(f"{k} is an invalid keyword argument")
+
+ t = security.token(evaluate_claims=security.CLAIMS_EVALUATION_ALWAYS)
+
+ for k, v in norm_args.items():
+ setattr(t, k, v)
+ if isinstance(v, list):
+ setattr(t, 'num_' + k, len(v))
+
+ return t