SIDs instead of names for groups
This commit is contained in:
@@ -18,10 +18,11 @@ USER_3_DN = "CN=Carol,OU=Users,DC=example,DC=com"
|
||||
COMPUTER_DN = "CN=PC01,OU=Computers,DC=example,DC=com"
|
||||
|
||||
|
||||
def principal(dn, sam, classes, members=(), member_of=()):
|
||||
def principal(dn, sam, classes, members=(), member_of=(), sid=""):
|
||||
return {
|
||||
"dn": dn,
|
||||
"objectGUID": "",
|
||||
"objectSid": sid,
|
||||
"samAccountName": sam,
|
||||
"objectClasses": set(classes),
|
||||
"memberDns": list(members),
|
||||
@@ -40,6 +41,22 @@ class GroupFolderNameTests(unittest.TestCase):
|
||||
self.assertEqual(rs.sanitize_group_folder_name("Finance."), "Finance")
|
||||
|
||||
|
||||
class SidParsingTests(unittest.TestCase):
|
||||
def test_parses_textual_and_binary_group_sid(self):
|
||||
expected = "S-1-5-21-111-222-333-513"
|
||||
raw = bytes((1, 5)) + (5).to_bytes(6, "big") + b"".join(
|
||||
value.to_bytes(4, "little")
|
||||
for value in (21, 111, 222, 333, 513)
|
||||
)
|
||||
|
||||
self.assertEqual(rs.parse_sid(expected.lower(), False), expected)
|
||||
self.assertEqual(
|
||||
rs.parse_sid(base64.b64encode(raw).decode("ascii"), True),
|
||||
expected,
|
||||
)
|
||||
self.assertEqual(rs.sid_rid(expected), 513)
|
||||
|
||||
|
||||
class LdapParsingTests(unittest.TestCase):
|
||||
def test_parser_keeps_repeated_members_and_unfolds_lines(self):
|
||||
display_name = base64.b64encode(b"Data Folder").decode("ascii")
|
||||
@@ -75,6 +92,7 @@ member;range=2-2: CN=Ranged,OU=Groups,DC=example,DC=com
|
||||
output = f"""
|
||||
dn: {ROOT_DN}
|
||||
objectGUID: 550e8400-e29b-41d4-a716-446655440000
|
||||
objectSid: S-1-5-21-111-222-333-1001
|
||||
objectClass: group
|
||||
sAMAccountName: FS_Data
|
||||
displayName: Data Folder
|
||||
@@ -88,6 +106,7 @@ memberOf: CN=Parent,OU=Groups,DC=example,DC=com
|
||||
|
||||
self.assertEqual(len(groups), 1)
|
||||
self.assertEqual(groups[0]["samAccountName"], "FS_Data")
|
||||
self.assertEqual(groups[0]["objectSid"], "S-1-5-21-111-222-333-1001")
|
||||
self.assertEqual(groups[0]["shareName"], "Data Folder")
|
||||
self.assertEqual(groups[0]["distinguishedName"], ROOT_DN)
|
||||
self.assertEqual(groups[0]["memberDns"], [GROUP_A_DN])
|
||||
@@ -111,6 +130,13 @@ memberOf: CN=Parent,OU=Groups,DC=example,DC=com
|
||||
f"(memberOf:{rs.LDAP_MATCHING_RULE_IN_CHAIN}:={ROOT_DN}))",
|
||||
)
|
||||
|
||||
def test_primary_group_filter_is_numeric_and_deduplicated(self):
|
||||
self.assertEqual(
|
||||
rs.build_primary_group_users_filter([513, 513, 515]),
|
||||
"(&(objectClass=user)(!(objectClass=computer))"
|
||||
"(|(primaryGroupID=513)(primaryGroupID=515)))",
|
||||
)
|
||||
|
||||
|
||||
class MembershipExpansionTests(unittest.TestCase):
|
||||
def lookup_from(self, principals):
|
||||
@@ -137,7 +163,7 @@ class MembershipExpansionTests(unittest.TestCase):
|
||||
}
|
||||
lookup = self.lookup_from(
|
||||
[
|
||||
principal(GROUP_A_DN, "GroupA", {"group"}, member_of=[ROOT_DN]),
|
||||
principal(GROUP_A_DN, "GroupA", {"group"}, member_of=[ROOT_DN], sid="S-1-5-21-1-2-3-1101"),
|
||||
principal(GROUP_B_DN, "GroupB", {"group"}, member_of=[GROUP_A_DN]),
|
||||
]
|
||||
)
|
||||
@@ -145,6 +171,10 @@ class MembershipExpansionTests(unittest.TestCase):
|
||||
expansion = rs.expand_group_membership(root_group, lookup_func=lookup)
|
||||
|
||||
self.assertEqual(expansion.group_sams, ["GroupA", "GroupB"])
|
||||
self.assertEqual(
|
||||
expansion.group_sids,
|
||||
{"groupa": "S-1-5-21-1-2-3-1101"},
|
||||
)
|
||||
self.assertEqual(expansion.unresolved_dns, [])
|
||||
self.assertEqual(expansion.cycle_paths, [["FS_Data", "GroupA", "FS_Data"]])
|
||||
|
||||
@@ -202,6 +232,31 @@ class MembershipExpansionTests(unittest.TestCase):
|
||||
|
||||
|
||||
class AclResolutionTests(unittest.TestCase):
|
||||
def test_sid_resolution_handles_localized_primary_group_name(self):
|
||||
names_seen = []
|
||||
|
||||
def name_resolver(workgroup, group_name):
|
||||
names_seen.append((workgroup, group_name))
|
||||
return None
|
||||
|
||||
gids, unresolved = rs.resolve_group_gids_for_acl(
|
||||
"BEISPIEL",
|
||||
["FS_Daten", "Domänen-Benutzer"],
|
||||
name_resolver,
|
||||
group_sids=[
|
||||
"S-1-5-21-111-222-333-1200",
|
||||
"S-1-5-21-111-222-333-513",
|
||||
],
|
||||
sid_resolver=lambda sid: {
|
||||
"S-1-5-21-111-222-333-1200": 11200,
|
||||
"S-1-5-21-111-222-333-513": 10513,
|
||||
}.get(sid),
|
||||
)
|
||||
|
||||
self.assertEqual(gids, [11200, 10513])
|
||||
self.assertEqual(unresolved, [])
|
||||
self.assertEqual(names_seen, [])
|
||||
|
||||
def test_gid_resolution_preserves_order_dedupes_and_reports_missing(self):
|
||||
mapping = {"FS_Data": 1001, "Nested": 1002}
|
||||
|
||||
|
||||
+56
-1
@@ -65,6 +65,55 @@ class DomainAuthenticationTests(unittest.TestCase):
|
||||
self.assertNotIn("p@ss word", run.call_args_list[0].args[0])
|
||||
|
||||
|
||||
class DirectoryPrimaryGroupTests(unittest.TestCase):
|
||||
def test_tree_expands_users_whose_primary_group_is_nested(self):
|
||||
domain_users_dn = "CN=Domänen-Benutzer,CN=Users,DC=example,DC=com"
|
||||
frank_dn = "CN=Frank,CN=Users,DC=example,DC=com"
|
||||
root = {
|
||||
"objectGUID": "root-guid",
|
||||
"objectSid": "S-1-5-21-111-222-333-1200",
|
||||
"samAccountName": "FS_Alle",
|
||||
"shareName": "Alle",
|
||||
"distinguishedName": "CN=FS_Alle,CN=Users,DC=example,DC=com",
|
||||
"memberDns": [domain_users_dn],
|
||||
"objectClasses": {"group"},
|
||||
}
|
||||
domain_users = {
|
||||
"distinguishedname": [(domain_users_dn, False)],
|
||||
"objectsid": [("S-1-5-21-111-222-333-513", False)],
|
||||
"samaccountname": [("Domänen-Benutzer", False)],
|
||||
"displayname": [("Domänen-Benutzer", False)],
|
||||
"objectclass": [("group", False)],
|
||||
}
|
||||
frank = {
|
||||
"distinguishedname": [(frank_dn, False)],
|
||||
"samaccountname": [("frank", False)],
|
||||
"displayname": [("Frank", False)],
|
||||
"primarygroupid": [("513", False)],
|
||||
"objectclass": [("user", False)],
|
||||
}
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir, mock.patch.object(
|
||||
web_ui, "STATE_DB", os.path.join(tmpdir, "missing.db")
|
||||
), mock.patch.object(
|
||||
web_ui.directory, "fetch_fileshare_groups", return_value=[root]
|
||||
), mock.patch.object(
|
||||
web_ui.directory,
|
||||
"search_directory_entries",
|
||||
side_effect=[[domain_users], [frank]],
|
||||
):
|
||||
result = web_ui.DirectoryCache().fetch()
|
||||
|
||||
nested_group = result["groups"][0]["members"][0]
|
||||
self.assertEqual(nested_group["sam"], "Domänen-Benutzer")
|
||||
self.assertEqual(
|
||||
[member["sam"] for member in nested_group["members"]],
|
||||
["frank"],
|
||||
)
|
||||
self.assertEqual(result["groups"][0]["userCount"], 1)
|
||||
self.assertFalse(result["truncated"])
|
||||
|
||||
|
||||
class AuditParsingTests(unittest.TestCase):
|
||||
def test_parses_full_audit_record(self):
|
||||
line = (
|
||||
@@ -420,12 +469,18 @@ class WebPresentationTests(unittest.TestCase):
|
||||
self.assertNotIn("localTime", script)
|
||||
self.assertIn('class="timestamp"', script)
|
||||
self.assertIn(".timestamp { text-align: left;", css)
|
||||
self.assertIn("Umbenennen/Verschieben", script)
|
||||
self.assertIn("Umbenennen", script)
|
||||
self.assertNotIn("Umbenennen/Verschieben", script)
|
||||
self.assertIn("Löschen", script)
|
||||
self.assertNotIn(">Auflisten<", script)
|
||||
self.assertNotIn(">Metadaten<", script)
|
||||
self.assertNotIn(">Sitzung<", script)
|
||||
self.assertNotIn('name="operation"', script)
|
||||
self.assertIn('class="shares-view"', script)
|
||||
self.assertIn('class="split shares-split"', script)
|
||||
self.assertIn(".shares-view { display: flex; height: calc(100vh - 4.5rem);", css)
|
||||
self.assertIn(".shares-split .list", css)
|
||||
self.assertIn(".shares-split .tree", css)
|
||||
|
||||
def test_samba_audits_only_supported_file_operations(self):
|
||||
path = os.path.join(os.path.dirname(__file__), "..", "etc", "samba", "smb.conf")
|
||||
|
||||
Reference in New Issue
Block a user