fix(m365): paginate admincenter group enumeration (#11510)

This commit is contained in:
Hugo Pereira Brito
2026-06-09 15:23:35 +02:00
committed by GitHub
parent 58efb719fa
commit d9f90e50b8
3 changed files with 64 additions and 11 deletions
+1
View File
@@ -21,6 +21,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
- `load_and_validate_config_file` now unwraps namespaced config for every built-in and external provider, and no longer leaks the full file as the provider's config when the file is namespaced [(#10700)](https://github.com/prowler-cloud/prowler/pull/10700)
- M365 Admin Center group enumeration now follows Microsoft Graph pagination so group-scoped checks include groups beyond the first page [(#11510)](https://github.com/prowler-cloud/prowler/pull/11510)
---
@@ -175,17 +175,23 @@ class AdminCenter(M365Service):
try:
groups_list = await self.client.groups.get()
groups.update({})
for group in groups_list.value:
groups.update(
{
group.id: Group(
id=group.id,
name=getattr(group, "display_name", ""),
visibility=getattr(group, "visibility", ""),
group_types=getattr(group, "group_types", []) or [],
)
}
)
while groups_list:
for group in getattr(groups_list, "value", []) or []:
groups.update(
{
group.id: Group(
id=group.id,
name=getattr(group, "display_name", ""),
visibility=getattr(group, "visibility", ""),
group_types=getattr(group, "group_types", []) or [],
)
}
)
next_link = getattr(groups_list, "odata_next_link", None)
if not next_link:
break
groups_list = await self.client.groups.with_url(next_link).get()
except Exception as error:
logger.error(
@@ -252,3 +252,49 @@ def test_admincenter__get_groups_maps_group_types():
assert groups["id-2"].group_types == []
assert groups["id-3"].group_types == []
assert groups["id-3"].visibility == "Public"
def test_admincenter__get_groups_handles_pagination():
admincenter_service = AdminCenter.__new__(AdminCenter)
groups_response_page_one = SimpleNamespace(
value=[
SimpleNamespace(
id="id-1",
display_name="First Unified Group",
visibility="Private",
group_types=["Unified"],
)
],
odata_next_link="next-link",
)
groups_response_page_two = SimpleNamespace(
value=[
SimpleNamespace(
id="id-2",
display_name="Second Unified Group",
visibility="Public",
group_types=["Unified"],
)
],
odata_next_link=None,
)
groups_with_url_builder = SimpleNamespace(
get=AsyncMock(return_value=groups_response_page_two)
)
with_url_mock = MagicMock(return_value=groups_with_url_builder)
groups_builder = SimpleNamespace(
get=AsyncMock(return_value=groups_response_page_one),
with_url=with_url_mock,
)
admincenter_service.client = SimpleNamespace(groups=groups_builder)
groups = asyncio.run(admincenter_service._get_groups())
assert len(groups) == 2
assert groups_builder.get.await_count == 1
with_url_mock.assert_called_once_with("next-link")
assert groups["id-1"].name == "First Unified Group"
assert groups["id-2"].name == "Second Unified Group"
assert groups["id-2"].visibility == "Public"