From e518a869ab552815aeb724359ee02f5b97251a00 Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Wed, 5 Feb 2025 01:47:14 +0100 Subject: [PATCH] feat(entra): add new `entra` service for Microsoft365 (#6326) Co-authored-by: MrCloudSec --- .../microsoft365/services/entra/__init__.py | 0 .../services/entra/entra_client.py | 4 + .../services/entra/entra_service.py | 105 ++++++++++++++++++ .../entra/microsoft365_entra_service_test.py | 45 ++++++++ 4 files changed, 154 insertions(+) create mode 100644 prowler/providers/microsoft365/services/entra/__init__.py create mode 100644 prowler/providers/microsoft365/services/entra/entra_client.py create mode 100644 prowler/providers/microsoft365/services/entra/entra_service.py create mode 100644 tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py diff --git a/prowler/providers/microsoft365/services/entra/__init__.py b/prowler/providers/microsoft365/services/entra/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/microsoft365/services/entra/entra_client.py b/prowler/providers/microsoft365/services/entra/entra_client.py new file mode 100644 index 0000000000..1a3b921adf --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_client.py @@ -0,0 +1,4 @@ +from prowler.providers.common.provider import Provider +from prowler.providers.microsoft365.services.entra.entra_service import Entra + +entra_client = Entra(Provider.get_global_provider()) diff --git a/prowler/providers/microsoft365/services/entra/entra_service.py b/prowler/providers/microsoft365/services/entra/entra_service.py new file mode 100644 index 0000000000..8561a31edd --- /dev/null +++ b/prowler/providers/microsoft365/services/entra/entra_service.py @@ -0,0 +1,105 @@ +from asyncio import gather, get_event_loop +from typing import List, Optional + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.microsoft365.lib.service.service import Microsoft365Service +from prowler.providers.microsoft365.microsoft365_provider import Microsoft365Provider + + +class Entra(Microsoft365Service): + def __init__(self, provider: Microsoft365Provider): + super().__init__(provider) + + loop = get_event_loop() + + attributes = loop.run_until_complete( + gather( + self._get_authorization_policy(), + ) + ) + + self.authorization_policy = attributes[0] + + async def _get_authorization_policy(self): + logger.info("Entra - Getting authorization policy...") + + authorization_policy = {} + try: + auth_policy = await self.client.policies.authorization_policy.get() + + default_user_role_permissions = getattr( + auth_policy, "default_user_role_permissions", None + ) + + authorization_policy.update( + { + auth_policy.id: AuthorizationPolicy( + id=auth_policy.id, + name=auth_policy.display_name, + description=auth_policy.description, + default_user_role_permissions=DefaultUserRolePermissions( + allowed_to_create_apps=getattr( + default_user_role_permissions, + "allowed_to_create_apps", + None, + ), + allowed_to_create_security_groups=getattr( + default_user_role_permissions, + "allowed_to_create_security_groups", + None, + ), + allowed_to_create_tenants=getattr( + default_user_role_permissions, + "allowed_to_create_tenants", + None, + ), + allowed_to_read_bitlocker_keys_for_owned_device=getattr( + default_user_role_permissions, + "allowed_to_read_bitlocker_keys_for_owned_device", + None, + ), + allowed_to_read_other_users=getattr( + default_user_role_permissions, + "allowed_to_read_other_users", + None, + ), + odata_type=getattr( + default_user_role_permissions, "odata_type", None + ), + permission_grant_policies_assigned=[ + policy_assigned + for policy_assigned in getattr( + default_user_role_permissions, + "permission_grant_policies_assigned", + [], + ) + ], + ), + ) + } + ) + except Exception as error: + logger.error( + f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + return authorization_policy + + +class DefaultUserRolePermissions(BaseModel): + allowed_to_create_apps: Optional[bool] + allowed_to_create_security_groups: Optional[bool] + allowed_to_create_tenants: Optional[bool] + allowed_to_read_bitlocker_keys_for_owned_device: Optional[bool] + allowed_to_read_other_users: Optional[bool] + odata_type: Optional[str] + permission_grant_policies_assigned: Optional[List[str]] = None + + +class AuthorizationPolicy(BaseModel): + id: str + name: str + description: str + default_user_role_permissions: Optional[DefaultUserRolePermissions] diff --git a/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py b/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py new file mode 100644 index 0000000000..86109478e2 --- /dev/null +++ b/tests/providers/microsoft365/services/entra/microsoft365_entra_service_test.py @@ -0,0 +1,45 @@ +from unittest.mock import patch + +from prowler.providers.microsoft365.models import Microsoft365IdentityInfo +from prowler.providers.microsoft365.services.entra.entra_service import ( + AuthorizationPolicy, + Entra, +) +from tests.providers.microsoft365.microsoft365_fixtures import ( + DOMAIN, + set_mocked_microsoft365_provider, +) + + +async def mock_entra_get_authorization_policy(_): + return { + "id-1": AuthorizationPolicy( + id="id-1", + name="Name 1", + description="Description 1", + default_user_role_permissions=None, + ) + } + + +@patch( + "prowler.providers.microsoft365.services.entra.entra_service.Entra._get_authorization_policy", + new=mock_entra_get_authorization_policy, +) +class Test_Entra_Service: + def test_get_client(self): + admincenter_client = Entra( + set_mocked_microsoft365_provider( + identity=Microsoft365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + assert admincenter_client.client.__class__.__name__ == "GraphServiceClient" + + def test_get_authorization_policy(self): + entra_client = Entra(set_mocked_microsoft365_provider()) + assert entra_client.authorization_policy["id-1"].id == "id-1" + assert entra_client.authorization_policy["id-1"].name == "Name 1" + assert entra_client.authorization_policy["id-1"].description == "Description 1" + assert not entra_client.authorization_policy[ + "id-1" + ].default_user_role_permissions