Skip to content

Commit

Permalink
[Network Drive] [DLS] Fix precedence for Deny permissions (#2004)
Browse files Browse the repository at this point in the history
  • Loading branch information
praveen-kukreja authored Jan 11, 2024
1 parent 24cf9b4 commit 4a53fa0
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 84 deletions.
121 changes: 78 additions & 43 deletions connectors/sources/network_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
)

ACCESS_ALLOWED_TYPE = 0
ACCESS_DENIED_TYPE = 1
ACCESS_MASK_ALLOWED_WRITE_PERMISSION = 1048854
ACCESS_MASK_DENIED_WRITE_PERMISSION = 278
GET_USERS_COMMAND = "Get-LocalUser | Select Name, SID"
GET_GROUPS_COMMAND = "Get-LocalGroup | Select-Object Name, SID"
Expand All @@ -68,8 +70,8 @@ def _prefix_user(user):
return prefix_identity("user", user)


def _prefix_sid(sid):
return prefix_identity("sid", sid)
def _prefix_rid(rid):
return prefix_identity("rid", rid)


class InvalidRulesError(Exception):
Expand Down Expand Up @@ -510,40 +512,36 @@ def _dls_enabled(self):

return self.configuration["use_document_level_security"]

async def _decorate_with_access_control(self, document, file_path, file_type):
async def _decorate_with_access_control(
self, document, file_path, file_type, groups_info
):
if self._dls_enabled():
entity_permissions = await self.get_entity_permission(
file_path=file_path, file_type=file_type
allow_permissions, deny_permissions = await self.get_entity_permission(
file_path=file_path, file_type=file_type, groups_info=groups_info
)
entity_permissions = list(set(allow_permissions) - set(deny_permissions))
document[ACCESS_CONTROL] = list(
set(document.get(ACCESS_CONTROL, []) + entity_permissions)
)
return document

async def _user_access_control_doc(
self, user, sid, groups_info, groups_members=None
):
async def _user_access_control_doc(self, user, sid, groups_info=None):
rid = str(sid).split("-")[-1]
prefixed_username = _prefix_user(user)
sid_user = _prefix_sid(sid)
sid_groups = []

if groups_members:
for group_name, group_sid in groups_info.items():
members = groups_members.get(group_name)
rid_user = _prefix_rid(rid)
rid_groups = []

if sid in members.values():
sid_groups.append(_prefix_sid(group_sid))
else:
for group_sid in groups_info or []:
sid_groups.append(_prefix_sid(group_sid))
for group_sid in groups_info or []:
rid = group_sid.split("-")[-1]
rid_groups.append(_prefix_rid(rid))

access_control = [sid_user, prefixed_username, *sid_groups]
access_control = [rid_user, prefixed_username, *rid_groups]

return {
"_id": sid,
"_id": rid,
"identity": {
"username": prefixed_username,
"user_id": sid_user,
"user_id": rid_user,
},
"created_at": iso_utc(),
} | es_access_control_query(access_control)
Expand All @@ -567,6 +565,21 @@ def read_user_info_csv(self):
)
return user_info

async def fetch_groups_info(self):
self._logger.info(
f"Fetching all groups and members for drive at path '{self.drive_path}'"
)
groups_info = await asyncio.to_thread(self.security_info.fetch_groups)

groups_members = {}
for group_name, group_sid in groups_info.items():
rid = group_sid.split("-")[-1]
groups_members[rid] = await asyncio.to_thread(
self.security_info.fetch_members, group_name
)

return groups_members

async def get_access_control(self):
if not self._dls_enabled():
self._logger.warning("DLS is not enabled. Skipping")
Expand All @@ -590,17 +603,6 @@ async def get_access_control(self):
raise ConfigurableFieldValueError(msg)
else:
try:
self._logger.info(
f"Fetching all groups and members for drive at path '{self.drive_path}'"
)
groups_info = await asyncio.to_thread(self.security_info.fetch_groups)

groups_members = {}
for group_name, _ in groups_info.items():
groups_members[group_name] = await asyncio.to_thread(
self.security_info.fetch_members, group_name
)

self._logger.info(
f"Fetching all users for drive at path '{self.drive_path}'"
)
Expand All @@ -610,18 +612,24 @@ async def get_access_control(self):
yield await self._user_access_control_doc(
user=user,
sid=sid,
groups_info=groups_info,
groups_members=groups_members,
)
except ConnectionError as exception:
msg = "Something went wrong"
raise ConnectionError(msg) from exception

async def get_entity_permission(self, file_path, file_type):
async def get_entity_permission(self, file_path, file_type, groups_info):
"""Processes permissions for a network drive, focusing on key terms:
- SID (Security Identifier): The unique identifier for a user or group, it undergoes revision.
- RID (Relative Identifier): The last part of a SID, uniquely identifying a user or group within a domain.
- ACE (Access Control Entry): An entry in `an Access Control List (ACL), defining permissions for a specific user or group. 0 is allowed ACE, 1 is denied ACE.
- Mask: A bit field representing allowed or denied permissions within an ACE. Example: Deny write, Allow read, etc.
"""
if not self._dls_enabled():
return []

permissions = []
allow_permissions = []
deny_permissions = []
if file_type == "file":
list_permissions = await asyncio.to_thread(
self.list_file_permission,
Expand All @@ -639,13 +647,37 @@ async def get_entity_permission(self, file_path, file_type):
access=DirectoryAccessMask.READ_CONTROL,
)
for permission in list_permissions or []:
# Access mask indicates specific permission within an ACE, such as read in deny ACE.
mask = permission["mask"].value

# Determine the type of ACE (access control entry), i.e, allow or deny
ace_type = permission["ace_type"].value

# Extract RID from SID. RID uniquely identifying a user or group within a domain.
rid = str(permission["sid"]).split("-")[-1]

if groups_info.get(rid):
# If the RID corresponds to a group, get the RIDs of all members of that group
permissions = [
_prefix_rid(member_id.split("-")[-1])
for member_id in groups_info[rid].values()
]
else:
# Else the RID corresponds to a user, hence we use it directly.
permissions = [_prefix_rid(rid)]
if (
ace_type == ACCESS_ALLOWED_TYPE
or mask == ACCESS_MASK_DENIED_WRITE_PERMISSION
):
allow_permissions.extend(permissions)

if (
permission["ace_type"].value == ACCESS_ALLOWED_TYPE
or permission["mask"].value == ACCESS_MASK_DENIED_WRITE_PERMISSION
ace_type == ACCESS_DENIED_TYPE
and mask != ACCESS_MASK_DENIED_WRITE_PERMISSION
):
permissions.append(_prefix_sid(permission["sid"]))
deny_permissions.extend(permissions)

return permissions
return allow_permissions, deny_permissions

async def get_docs(self, filtering=None):
"""Executes the logic to fetch files and folders in async manner.
Expand All @@ -669,14 +701,17 @@ async def get_docs(self, filtering=None):

else:
matched_paths = (path for path, _, _ in self.get_directory_details)
groups_info = {}
if self.drive_type == WINDOWS:
groups_info = await self.fetch_groups_info()

for path in matched_paths:
async for file in self.get_files(path=path):
if file["type"] == "folder":
yield await self._decorate_with_access_control(
file, file.get("path"), file.get("type")
file, file.get("path"), file.get("type"), groups_info
), None
else:
yield await self._decorate_with_access_control(
file, file.get("path"), file.get("type")
file, file.get("path"), file.get("type"), groups_info
), partial(self.get_content, file)
2 changes: 1 addition & 1 deletion tests/sources/fixtures/network_drive/connector.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"order":7,
"type": "str",
"ui_restrictions": ["advanced"],
"value":"windows"
"value":"linux"
},
"identity_mappings": {
"label": "Path of CSV file containing users and groups SID (For Linux Network Drive)",
Expand Down
Loading

0 comments on commit 4a53fa0

Please sign in to comment.