ctdb-scripts: Improve update and listing code
[samba4-gss.git] / python / samba / tests / token_factory.py
blobe4e5d87df015c9df811102eb2fdf3152664b5fdf
1 # Unix SMB/CIFS implementation.
2 # Copyright © Catalyst IT 2023
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 """An API for creating arbitrary security tokens."""
20 from samba.dcerpc import security
23 CLAIM_VAL_TYPES = {
24 int: 0x0001,
25 'uint': 0x0002,
26 str: 0x0003,
27 security.dom_sid: 0x0005,
28 bool: 0x0006,
29 bytes: 0x0010
33 def list_to_claim(k, v, case_sensitive=False):
34 if isinstance(v, security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1):
35 # make the name match
36 v.name = k
37 return v
38 if isinstance(v, (str, int)):
39 v = [v]
40 if not isinstance(v, list):
41 raise TypeError(f"expected list of claim values for '{k}', "
42 f"not {v!r} of type {type(v)}")
44 c = security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1()
46 if len(v) != 0:
47 t = type(v[0])
48 c.value_type = CLAIM_VAL_TYPES[t]
49 for val in v[1:]:
50 if type(val) is not t:
51 raise TypeError(f"claim values for '{k}' "
52 "should all be the same type")
53 else:
54 # pick an arbitrary type
55 c.value_type = CLAIM_VAL_TYPES['uint']
56 c.name = k
57 c.values = v
58 c.value_count = len(v)
59 if case_sensitive:
60 c.flags |= security.CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE
62 # The claims made here will not have the
63 # CLAIM_SECURITY_ATTRIBUTE_UNIQUE_AND_SORTED flag set, which makes
64 # them like resource attribute claims rather than real wire
65 # claims. It shouldn't matter much, as they will just be sorted
66 # and checked as if they were resource attribute claims.
67 return c
70 def _normalise_claims(args):
71 if isinstance(args, security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1):
72 return [args]
74 if args is None or len(args) == 0:
75 return []
77 if isinstance(args, list):
78 for x in args:
79 if not isinstance(x, security.CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1):
80 raise TypeError(f"list should be of claims, not '{type(x)}'")
81 return args
83 claims_out = []
85 if isinstance(args, dict):
86 # the key is the name and the value is a list of claim values
87 for k, v in args.items():
88 c = list_to_claim(k, v)
89 claims_out.append(c)
91 return claims_out
94 def str_to_sid(s):
95 lut = {
96 # These are a subset of two letter aliases that don't need a
97 # domain SID or other magic. (c.f. sid_strings test).
98 'AA': security.SID_BUILTIN_ACCESS_CONTROL_ASSISTANCE_OPS, # S-1-5-32-579
99 'AC': security.SID_SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE, # S-1-15-2-1
100 'AN': security.SID_NT_ANONYMOUS, # S-1-5-7
101 'AO': security.SID_BUILTIN_ACCOUNT_OPERATORS, # S-1-5-32-548
102 'AS': security.SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY, # S-1-18-1
103 'AU': security.SID_NT_AUTHENTICATED_USERS, # S-1-5-11
104 'BA': security.SID_BUILTIN_ADMINISTRATORS, # S-1-5-32-544
105 'BG': security.SID_BUILTIN_GUESTS, # S-1-5-32-546
106 'BO': security.SID_BUILTIN_BACKUP_OPERATORS, # S-1-5-32-551
107 'BU': security.SID_BUILTIN_USERS, # S-1-5-32-545
108 'CD': security.SID_BUILTIN_CERT_SERV_DCOM_ACCESS, # S-1-5-32-574
109 'CG': security.SID_CREATOR_GROUP, # S-1-3-1
110 'CO': security.SID_CREATOR_OWNER, # S-1-3-0
111 'CY': security.SID_BUILTIN_CRYPTO_OPERATORS, # S-1-5-32-569
112 'ED': security.SID_NT_ENTERPRISE_DCS, # S-1-5-9
113 'ER': security.SID_BUILTIN_EVENT_LOG_READERS, # S-1-5-32-573
114 'ES': security.SID_BUILTIN_RDS_ENDPOINT_SERVERS, # S-1-5-32-576
115 'HA': security.SID_BUILTIN_HYPER_V_ADMINS, # S-1-5-32-578
116 'HI': security.SID_SECURITY_MANDATORY_HIGH, # S-1-16-12288
117 'IS': security.SID_BUILTIN_IUSERS, # S-1-5-32-568
118 'IU': security.SID_NT_INTERACTIVE, # S-1-5-4
119 'LS': security.SID_NT_LOCAL_SERVICE, # S-1-5-19
120 'LU': security.SID_BUILTIN_PERFLOG_USERS, # S-1-5-32-559
121 'LW': security.SID_SECURITY_MANDATORY_LOW, # S-1-16-4096
122 'ME': security.SID_SECURITY_MANDATORY_MEDIUM, # S-1-16-8192
123 'MP': security.SID_SECURITY_MANDATORY_MEDIUM_PLUS, # S-1-16-8448
124 'MS': security.SID_BUILTIN_RDS_MANAGEMENT_SERVERS, # S-1-5-32-577
125 'MU': security.SID_BUILTIN_PERFMON_USERS, # S-1-5-32-558
126 'NO': security.SID_BUILTIN_NETWORK_CONF_OPERATORS, # S-1-5-32-556
127 'NS': security.SID_NT_NETWORK_SERVICE, # S-1-5-20
128 'NU': security.SID_NT_NETWORK, # S-1-5-2
129 'OW': security.SID_OWNER_RIGHTS, # S-1-3-4
130 'PO': security.SID_BUILTIN_PRINT_OPERATORS, # S-1-5-32-550
131 'PS': security.SID_NT_SELF, # S-1-5-10
132 'PU': security.SID_BUILTIN_POWER_USERS, # S-1-5-32-547
133 'RA': security.SID_BUILTIN_RDS_REMOTE_ACCESS_SERVERS, # S-1-5-32-575
134 'RC': security.SID_NT_RESTRICTED, # S-1-5-12
135 'RD': security.SID_BUILTIN_REMOTE_DESKTOP_USERS, # S-1-5-32-555
136 'RE': security.SID_BUILTIN_REPLICATOR, # S-1-5-32-552
137 'RM': security.SID_BUILTIN_REMOTE_MANAGEMENT_USERS, # S-1-5-32-580
138 'RU': security.SID_BUILTIN_PREW2K, # S-1-5-32-554
139 'SI': security.SID_SECURITY_MANDATORY_SYSTEM, # S-1-16-16384
140 'SO': security.SID_BUILTIN_SERVER_OPERATORS, # S-1-5-32-549
141 'SS': security.SID_SERVICE_ASSERTED_IDENTITY, # S-1-18-2
142 'SU': security.SID_NT_SERVICE, # S-1-5-6
143 'SY': security.SID_NT_SYSTEM, # S-1-5-18
144 'WD': security.SID_WORLD, # S-1-1-0
145 'WR': security.SID_SECURITY_RESTRICTED_CODE, # S-1-5-33
147 if s in lut:
148 s = lut[s]
149 return security.dom_sid(s)
152 def _normalise_sids(args):
153 if isinstance(args, security.dom_sid):
154 return [args]
155 if isinstance(args, str):
156 return [str_to_sid(args)]
158 if not isinstance(args, list):
159 raise TypeError("expected a SID, sid string, or list of SIDs, "
160 f"not'{type(args)}'")
162 sids_out = []
163 for s in args:
164 if isinstance(s, str):
165 s = str_to_sid(s)
166 elif not isinstance(s, security.dom_sid):
167 raise TypeError(f"expected a SID, not'{type(s)}'")
168 sids_out.append(s)
170 return sids_out
173 def _normalise_mask(mask, mask_type):
174 if isinstance(mask, int):
175 return mask
177 if not isinstance(mask, list):
178 raise TypeError("expected int mask or list of flags")
180 if mask_type == 'privileges':
181 prefix = 'SEC_PRIV_'
182 tail = '_BIT'
183 elif mask_type == 'rights':
184 prefix = 'LSA_POLICY_MODE_'
185 tail = ''
186 else:
187 raise ValueError(f"unknown mask_type value: {mask_type}")
189 mask_out = 0
191 for x in mask:
192 if isinstance(x, str) and x.startswith(prefix):
193 if not x.endswith(tail):
194 # we don't want security.SEC_PRIV_SHUTDOWN (19),
195 # we want security.SEC_PRIV_SHUTDOWN_BIT (1 << 20)
196 # but you can write "SEC_PRIV_SHUTDOWN"
197 x += tail
198 x = getattr(security, x)
199 mask_out |= x
201 return mask_out
204 def token(sids=None, **kwargs):
205 """Return a security token with the specified attributes.
207 The security.token API is annoying and fragile; here we wrap it in
208 something nicer.
210 In general the arguments can either be objects of the correct
211 type, or Python strings or structures that clearly convert to that
212 type. For example, there two are equivalent:
214 >>> t = token([security.dom_sid("S-1-2")])
215 >>> t = token(["S-1-2"])
217 To add claims and device SIDs you do something like this:
219 >>> t = token(["AA", "WD"],
220 device_sids=["WD"],
221 user_claims={"Title": ["PM"],
222 "ClearanceLevel": [1]}
225 claims_kws = ['device_claims',
226 'local_claims',
227 'user_claims']
229 sid_kws = ['sids', 'device_sids']
231 mask_kws = ['privileges',
232 'rights']
234 if sids is not None:
235 kwargs['sids'] = sids
237 norm_args = {}
239 for k, v in kwargs.items():
240 if k in claims_kws:
241 norm_args[k] = _normalise_claims(v)
242 elif k in mask_kws:
243 norm_args[k] = _normalise_mask(v, k)
244 elif k in sid_kws:
245 norm_args[k] = _normalise_sids(v)
246 else:
247 raise TypeError(f"{k} is an invalid keyword argument")
249 t = security.token(evaluate_claims=security.CLAIMS_EVALUATION_ALWAYS)
251 for k, v in norm_args.items():
252 setattr(t, k, v)
253 if isinstance(v, list):
254 setattr(t, 'num_' + k, len(v))
256 return t