mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
chore(entra): enhance performance for user_registration_details and user mfa evaluation (#9236)
This commit is contained in:
committed by
GitHub
parent
2cde4c939d
commit
1bf49747ad
@@ -30,6 +30,7 @@ Assign the following Microsoft Graph permissions:
|
||||
- `Directory.Read.All`
|
||||
- `Policy.Read.All`
|
||||
- `UserAuthenticationMethod.Read.All` (optional, for multifactor authentication (MFA) checks)
|
||||
- `AuditLog.Read.All` (optional, for multifactor authentication (MFA) checks)
|
||||
|
||||
<Note>
|
||||
Replace `Directory.Read.All` with `Domain.Read.All` for more restrictive permissions. Note that Entra checks related to DirectoryRoles and GetUsers will not run with this permission.
|
||||
@@ -51,6 +52,7 @@ Replace `Directory.Read.All` with `Domain.Read.All` for more restrictive permiss
|
||||
- `Directory.Read.All`
|
||||
- `Policy.Read.All`
|
||||
- `UserAuthenticationMethod.Read.All`
|
||||
- `AuditLog.Read.All`
|
||||
|
||||

|
||||
|
||||
@@ -62,7 +64,7 @@ Replace `Directory.Read.All` with `Domain.Read.All` for more restrictive permiss
|
||||
1. To grant permissions to a Service Principal, execute the following command in a terminal:
|
||||
|
||||
```console
|
||||
az ad app permission add --id {appId} --api 00000003-0000-0000-c000-000000000000 --api-permissions 7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role 246dd0d5-5bd0-4def-940b-0421030a5b68=Role 38d9df27-64da-44fd-b7c5-a6fbac20248f=Role
|
||||
az ad app permission add --id {appId} --api 00000003-0000-0000-c000-000000000000 --api-permissions 7ab1d382-f21e-4acd-a863-ba3e13f7da61=Role 246dd0d5-5bd0-4def-940b-0421030a5b68=Role 38d9df27-64da-44fd-b7c5-a6fbac20248f=Role b0afded3-3588-46d8-8b3d-9842eff778da=Role
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -375,7 +377,7 @@ The ProwlerRole is a custom role required for specific security checks. First, c
|
||||
|
||||
#### Step 4: (Optional) Assign Microsoft Graph Permissions
|
||||
|
||||
For Entra ID (Azure AD) checks, the Managed Identity needs Microsoft Graph API permissions: `Directory.Read.All`, `Policy.Read.All`, and optionally `UserAuthenticationMethod.Read.All`.
|
||||
For Entra ID (Azure AD) checks, the Managed Identity needs Microsoft Graph API permissions: `Directory.Read.All`, `Policy.Read.All`, and optionally `UserAuthenticationMethod.Read.All` and `AuditLog.Read.All`.
|
||||
|
||||
<Note>
|
||||
Assigning Microsoft Graph API permissions to a Managed Identity requires Azure CLI or PowerShell - it cannot be done through the Azure Portal's standard role assignment interface.
|
||||
|
||||
@@ -49,6 +49,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- Update AWS RDS service metadata to new format [(#9551)](https://github.com/prowler-cloud/prowler/pull/9551)
|
||||
- Update AWS Bedrock service metadata to new format [(#8827)](https://github.com/prowler-cloud/prowler/pull/8827)
|
||||
- Update AWS IAM service metadata to new format [(#9550)](https://github.com/prowler-cloud/prowler/pull/9550)
|
||||
- Enhance `user_registration_details` perfomance and user `mfa` evaluation [(#9236)](https://github.com/prowler-cloud/prowler/pull/9236)
|
||||
- Update AWS Cognito service metadata to new format [(#8853)](https://github.com/prowler-cloud/prowler/pull/8853)
|
||||
|
||||
---
|
||||
|
||||
@@ -21,7 +21,7 @@ class entra_non_privileged_user_has_mfa(Check):
|
||||
f"Non-privileged user {user.name} does not have MFA."
|
||||
)
|
||||
|
||||
if len(user.authentication_methods) > 1:
|
||||
if user.is_mfa_capable:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Non-privileged user {user.name} has MFA."
|
||||
|
||||
@@ -21,7 +21,7 @@ class entra_privileged_user_has_mfa(Check):
|
||||
f"Privileged user {user.name} does not have MFA."
|
||||
)
|
||||
|
||||
if len(user.authentication_methods) > 1:
|
||||
if user.is_mfa_capable:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Privileged user {user.name} has MFA."
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class Entra(AzureService):
|
||||
for tenant, client in self.clients.items():
|
||||
users.update({tenant: {}})
|
||||
users_response = await client.users.get()
|
||||
registration_details = await self._get_user_registration_details(client)
|
||||
|
||||
try:
|
||||
while users_response:
|
||||
@@ -75,19 +76,9 @@ class Entra(AzureService):
|
||||
user.id: User(
|
||||
id=user.id,
|
||||
name=user.display_name,
|
||||
authentication_methods=[
|
||||
AuthMethod(
|
||||
id=auth_method.id,
|
||||
type=getattr(
|
||||
auth_method, "odata_type", None
|
||||
),
|
||||
)
|
||||
for auth_method in (
|
||||
await client.users.by_user_id(
|
||||
user.id
|
||||
).authentication.methods.get()
|
||||
).value
|
||||
],
|
||||
is_mfa_capable=registration_details.get(
|
||||
user.id, False
|
||||
),
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -116,6 +107,34 @@ class Entra(AzureService):
|
||||
|
||||
return users
|
||||
|
||||
async def _get_user_registration_details(self, client):
|
||||
registration_details = {}
|
||||
try:
|
||||
registration_builder = (
|
||||
client.reports.authentication_methods.user_registration_details
|
||||
)
|
||||
registration_response = await registration_builder.get()
|
||||
|
||||
while registration_response:
|
||||
for detail in getattr(registration_response, "value", []) or []:
|
||||
registration_details.update(
|
||||
{detail.id: getattr(detail, "is_mfa_capable", False)}
|
||||
)
|
||||
|
||||
next_link = getattr(registration_response, "odata_next_link", None)
|
||||
if not next_link:
|
||||
break
|
||||
registration_response = await registration_builder.with_url(
|
||||
next_link
|
||||
).get()
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
return registration_details
|
||||
|
||||
async def _get_authorization_policy(self):
|
||||
logger.info("Entra - Getting authorization policy...")
|
||||
|
||||
@@ -391,15 +410,10 @@ class Entra(AzureService):
|
||||
return conditional_access_policy
|
||||
|
||||
|
||||
class AuthMethod(BaseModel):
|
||||
id: str
|
||||
type: str
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
authentication_methods: List[AuthMethod] = []
|
||||
is_mfa_capable: bool = False
|
||||
|
||||
|
||||
class DefaultUserRolePermissions(BaseModel):
|
||||
|
||||
@@ -43,7 +43,7 @@ class entra_user_with_vm_access_has_mfa(Check):
|
||||
report.subscription = subscription_name
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"User {user.name} without MFA can access VMs in subscription {subscription_name}"
|
||||
if len(user.authentication_methods) > 1:
|
||||
if user.is_mfa_capable:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"User {user.name} can access VMs in subscription {subscription_name} but it has MFA."
|
||||
|
||||
|
||||
@@ -402,18 +402,7 @@ class Entra(M365Service):
|
||||
for member in members:
|
||||
user_roles_map.setdefault(member.id, []).append(role_template_id)
|
||||
|
||||
try:
|
||||
registration_details_list = (
|
||||
await self.client.reports.authentication_methods.user_registration_details.get()
|
||||
)
|
||||
registration_details = {
|
||||
detail.id: detail for detail in registration_details_list.value
|
||||
}
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
registration_details = {}
|
||||
registration_details = await self._get_user_registration_details()
|
||||
|
||||
while users_response:
|
||||
for user in getattr(users_response, "value", []) or []:
|
||||
@@ -424,11 +413,7 @@ class Entra(M365Service):
|
||||
True if (user.on_premises_sync_enabled) else False
|
||||
),
|
||||
directory_roles_ids=user_roles_map.get(user.id, []),
|
||||
is_mfa_capable=(
|
||||
registration_details.get(user.id, {}).is_mfa_capable
|
||||
if registration_details.get(user.id, None) is not None
|
||||
else False
|
||||
),
|
||||
is_mfa_capable=(registration_details.get(user.id, False)),
|
||||
account_enabled=not self.user_accounts_status.get(
|
||||
user.id, {}
|
||||
).get("AccountDisabled", False),
|
||||
@@ -444,6 +429,38 @@ class Entra(M365Service):
|
||||
)
|
||||
return users
|
||||
|
||||
async def _get_user_registration_details(self):
|
||||
registration_details = {}
|
||||
try:
|
||||
registration_builder = (
|
||||
self.client.reports.authentication_methods.user_registration_details
|
||||
)
|
||||
registration_response = await registration_builder.get()
|
||||
|
||||
while registration_response:
|
||||
for detail in getattr(registration_response, "value", []) or []:
|
||||
registration_details.update(
|
||||
{detail.id: getattr(detail, "is_mfa_capable", False)}
|
||||
)
|
||||
|
||||
next_link = getattr(registration_response, "odata_next_link", None)
|
||||
if not next_link:
|
||||
break
|
||||
registration_response = await registration_builder.with_url(
|
||||
next_link
|
||||
).get()
|
||||
|
||||
except Exception as error:
|
||||
if (
|
||||
error.__class__.__name__ == "ODataError"
|
||||
and error.__dict__.get("response_status_code", None) == 403
|
||||
):
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
return registration_details
|
||||
|
||||
|
||||
class ConditionalAccessPolicyState(Enum):
|
||||
ENABLED = "enabled"
|
||||
|
||||
@@ -69,7 +69,6 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
entra_non_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -77,7 +76,7 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[AuthMethod(id=str(uuid4()), type="foo")],
|
||||
is_mfa_capable=False,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
@@ -117,7 +116,6 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
entra_non_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -125,10 +123,7 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="foo"),
|
||||
AuthMethod(id=str(uuid4()), type="bar"),
|
||||
],
|
||||
is_mfa_capable=True,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
@@ -165,7 +160,6 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
entra_non_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -173,7 +167,7 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[AuthMethod(id=str(uuid4()), type="foo")],
|
||||
is_mfa_capable=False,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
@@ -207,7 +201,6 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
entra_non_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -215,10 +208,7 @@ class Test_entra_non_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="foo"),
|
||||
AuthMethod(id=str(uuid4()), type="bar"),
|
||||
],
|
||||
is_mfa_capable=True,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
|
||||
@@ -69,7 +69,6 @@ class Test_entra_privileged_user_has_mfa:
|
||||
entra_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -77,7 +76,7 @@ class Test_entra_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[AuthMethod(id=str(uuid4()), type="foo")],
|
||||
is_mfa_capable=False,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
@@ -109,7 +108,6 @@ class Test_entra_privileged_user_has_mfa:
|
||||
entra_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -117,10 +115,7 @@ class Test_entra_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="foo"),
|
||||
AuthMethod(id=str(uuid4()), type="bar"),
|
||||
],
|
||||
is_mfa_capable=True,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
@@ -152,7 +147,6 @@ class Test_entra_privileged_user_has_mfa:
|
||||
entra_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -160,7 +154,7 @@ class Test_entra_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[AuthMethod(id=str(uuid4()), type="foo")],
|
||||
is_mfa_capable=False,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
@@ -199,7 +193,6 @@ class Test_entra_privileged_user_has_mfa:
|
||||
entra_privileged_user_has_mfa,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
DirectoryRole,
|
||||
User,
|
||||
)
|
||||
@@ -207,10 +200,7 @@ class Test_entra_privileged_user_has_mfa:
|
||||
user = User(
|
||||
id=user_id,
|
||||
name="foo",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="foo"),
|
||||
AuthMethod(id=str(uuid4()), type="bar"),
|
||||
],
|
||||
is_mfa_capable=True,
|
||||
)
|
||||
|
||||
entra_client.users = {DOMAIN: {f"foo@{DOMAIN}": user}}
|
||||
|
||||
@@ -145,10 +145,7 @@ class Test_Entra_Service:
|
||||
assert len(entra_client.users) == 1
|
||||
assert entra_client.users[DOMAIN]["user-1@tenant1.es"].id == "id-1"
|
||||
assert entra_client.users[DOMAIN]["user-1@tenant1.es"].name == "User 1"
|
||||
assert (
|
||||
len(entra_client.users[DOMAIN]["user-1@tenant1.es"].authentication_methods)
|
||||
== 0
|
||||
)
|
||||
assert entra_client.users[DOMAIN]["user-1@tenant1.es"].is_mfa_capable is False
|
||||
|
||||
def test_get_authorization_policy(self):
|
||||
entra_client = Entra(set_mocked_azure_provider())
|
||||
@@ -251,38 +248,48 @@ def test_azure_entra__get_users_handles_pagination():
|
||||
)
|
||||
with_url_mock = MagicMock(return_value=users_with_url_builder)
|
||||
|
||||
def by_user_id_side_effect(user_id):
|
||||
auth_methods_response = SimpleNamespace(
|
||||
value=[
|
||||
SimpleNamespace(
|
||||
id=f"{user_id}-method",
|
||||
odata_type="#microsoft.graph.passwordAuthenticationMethod",
|
||||
)
|
||||
]
|
||||
)
|
||||
return SimpleNamespace(
|
||||
authentication=SimpleNamespace(
|
||||
methods=SimpleNamespace(
|
||||
get=AsyncMock(return_value=auth_methods_response)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
users_builder = SimpleNamespace(
|
||||
get=AsyncMock(return_value=users_response_page_one),
|
||||
with_url=with_url_mock,
|
||||
by_user_id=MagicMock(side_effect=by_user_id_side_effect),
|
||||
)
|
||||
|
||||
entra_service.clients = {"tenant-1": SimpleNamespace(users=users_builder)}
|
||||
registration_details_response = SimpleNamespace(
|
||||
value=[
|
||||
SimpleNamespace(
|
||||
id="user-1",
|
||||
is_mfa_capable=True,
|
||||
),
|
||||
SimpleNamespace(
|
||||
id="user-2",
|
||||
is_mfa_capable=True,
|
||||
),
|
||||
],
|
||||
odata_next_link=None,
|
||||
)
|
||||
|
||||
registration_details_builder = SimpleNamespace(
|
||||
get=AsyncMock(return_value=registration_details_response),
|
||||
with_url=MagicMock(),
|
||||
)
|
||||
|
||||
entra_service.clients = {
|
||||
"tenant-1": SimpleNamespace(
|
||||
users=users_builder,
|
||||
reports=SimpleNamespace(
|
||||
authentication_methods=SimpleNamespace(
|
||||
user_registration_details=registration_details_builder
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
users = asyncio.run(entra_service._get_users())
|
||||
|
||||
assert len(users["tenant-1"]) == 3
|
||||
assert users_builder.get.await_count == 1
|
||||
with_url_mock.assert_called_once_with("next-link")
|
||||
assert users["tenant-1"]["user-1"].authentication_methods[0].id == "user-1-method"
|
||||
assert (
|
||||
users["tenant-1"]["user-3"].authentication_methods[0].type
|
||||
== "#microsoft.graph.passwordAuthenticationMethod"
|
||||
)
|
||||
registration_details_builder.get.assert_awaited()
|
||||
registration_details_builder.with_url.assert_not_called()
|
||||
assert users["tenant-1"]["user-1"].is_mfa_capable is True
|
||||
assert users["tenant-1"]["user-2"].is_mfa_capable is True
|
||||
assert users["tenant-1"]["user-3"].is_mfa_capable is False
|
||||
|
||||
@@ -61,10 +61,7 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
User,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import User
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
@@ -90,12 +87,7 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
f"test@{DOMAIN}": User(
|
||||
id=user_id,
|
||||
name="test",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="Password"),
|
||||
AuthMethod(
|
||||
id=str(uuid4()), type="MicrosoftAuthenticator"
|
||||
),
|
||||
],
|
||||
is_mfa_capable=True,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -138,10 +130,7 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
User,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import User
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
@@ -167,9 +156,7 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
f"test@{DOMAIN}": User(
|
||||
id=user_id,
|
||||
name="test",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="Password"),
|
||||
],
|
||||
is_mfa_capable=False,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -264,10 +251,7 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.entra.entra_service import (
|
||||
AuthMethod,
|
||||
User,
|
||||
)
|
||||
from prowler.providers.azure.services.entra.entra_service import User
|
||||
from prowler.providers.azure.services.entra.entra_user_with_vm_access_has_mfa.entra_user_with_vm_access_has_mfa import (
|
||||
entra_user_with_vm_access_has_mfa,
|
||||
)
|
||||
@@ -293,12 +277,7 @@ class Test_iam_assignment_priviledge_access_vm_has_mfa:
|
||||
f"test@{DOMAIN}": User(
|
||||
id=user_id,
|
||||
name="test",
|
||||
authentication_methods=[
|
||||
AuthMethod(id=str(uuid4()), type="Password"),
|
||||
AuthMethod(
|
||||
id=str(uuid4()), type="MicrosoftAuthenticator"
|
||||
),
|
||||
],
|
||||
is_mfa_capable=True,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,10 +401,14 @@ class Test_Entra_Service:
|
||||
value=[
|
||||
SimpleNamespace(id="user-1", is_mfa_capable=True),
|
||||
SimpleNamespace(id="user-6", is_mfa_capable=True),
|
||||
]
|
||||
],
|
||||
odata_next_link=None,
|
||||
)
|
||||
registration_details_builder = SimpleNamespace(
|
||||
get=AsyncMock(return_value=registration_details_response)
|
||||
get=AsyncMock(return_value=registration_details_response),
|
||||
with_url=MagicMock(
|
||||
return_value=SimpleNamespace(get=AsyncMock(return_value=None))
|
||||
),
|
||||
)
|
||||
reports_builder = SimpleNamespace(
|
||||
authentication_methods=SimpleNamespace(
|
||||
@@ -429,3 +433,44 @@ class Test_Entra_Service:
|
||||
assert users["user-6"].account_enabled is False
|
||||
assert users["user-1"].is_mfa_capable is True
|
||||
assert users["user-2"].is_mfa_capable is False
|
||||
|
||||
def test__get_user_registration_details_handles_pagination(self):
|
||||
entra_service = Entra.__new__(Entra)
|
||||
|
||||
registration_response_page_one = SimpleNamespace(
|
||||
value=[
|
||||
SimpleNamespace(id="user-1", is_mfa_capable=True),
|
||||
],
|
||||
odata_next_link="next-link",
|
||||
)
|
||||
registration_response_page_two = SimpleNamespace(
|
||||
value=[
|
||||
SimpleNamespace(id="user-2", is_mfa_capable=False),
|
||||
],
|
||||
odata_next_link=None,
|
||||
)
|
||||
|
||||
registration_builder_next = SimpleNamespace(
|
||||
get=AsyncMock(return_value=registration_response_page_two)
|
||||
)
|
||||
registration_builder = SimpleNamespace(
|
||||
get=AsyncMock(return_value=registration_response_page_one),
|
||||
with_url=MagicMock(return_value=registration_builder_next),
|
||||
)
|
||||
|
||||
entra_service.client = SimpleNamespace(
|
||||
reports=SimpleNamespace(
|
||||
authentication_methods=SimpleNamespace(
|
||||
user_registration_details=registration_builder
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
registration_details = asyncio.run(
|
||||
entra_service._get_user_registration_details()
|
||||
)
|
||||
|
||||
assert registration_details == {"user-1": True, "user-2": False}
|
||||
registration_builder.get.assert_awaited()
|
||||
registration_builder.with_url.assert_called_once_with("next-link")
|
||||
registration_builder_next.get.assert_awaited()
|
||||
|
||||
Reference in New Issue
Block a user