From 2ad50f52bfcdec22e62fd2925ab37bbc277893dc Mon Sep 17 00:00:00 2001
From: James Robinson <james.em.robinson@gmail.com>
Date: Tue, 21 May 2024 13:47:11 +0100
Subject: [PATCH 1/4] :bug: Continue processing groups even if attributes
 cannot be processed for one of them

---
 apricot/oauth/microsoft_entra_client.py | 35 ++++++++++++++-----------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/apricot/oauth/microsoft_entra_client.py b/apricot/oauth/microsoft_entra_client.py
index 4bc94c8..7ca1e07 100644
--- a/apricot/oauth/microsoft_entra_client.py
+++ b/apricot/oauth/microsoft_entra_client.py
@@ -1,5 +1,7 @@
 from typing import Any, cast
 
+from twisted.python import log
+
 from apricot.types import JSONDict
 
 from .oauth_client import OAuthClient
@@ -28,19 +30,19 @@ def extract_token(self, json_response: JSONDict) -> str:
 
     def groups(self) -> list[JSONDict]:
         output = []
-        try:
-            queries = [
-                "createdDateTime",
-                "displayName",
-                "id",
-            ]
-            group_data = self.query(
-                f"https://graph.microsoft.com/v1.0/groups?$select={','.join(queries)}"
-            )
-            for group_dict in cast(
-                list[JSONDict],
-                sorted(group_data["value"], key=lambda group: group["createdDateTime"]),
-            ):
+        queries = [
+            "createdDateTime",
+            "displayName",
+            "id",
+        ]
+        group_data = self.query(
+            f"https://graph.microsoft.com/v1.0/groups?$select={','.join(queries)}"
+        )
+        for group_dict in cast(
+            list[JSONDict],
+            sorted(group_data["value"], key=lambda group: group["createdDateTime"]),
+        ):
+            try:
                 group_uid = self.uid_cache.get_group_uid(group_dict["id"])
                 attributes: JSONDict = {}
                 attributes["cn"] = group_dict.get("displayName", None)
@@ -57,8 +59,11 @@ def groups(self) -> list[JSONDict]:
                     if user["userPrincipalName"]
                 ]
                 output.append(attributes)
-        except KeyError:
-            pass
+            except KeyError as exc:
+                msg = (
+                    f"Failed to process group {group_dict} due to a missing key {exc}."
+                )
+                log.msg(msg)
         return output
 
     def users(self) -> list[JSONDict]:

From 9ce7f3dc51f365da50481929911d78b74266fce2 Mon Sep 17 00:00:00 2001
From: James Robinson <james.em.robinson@gmail.com>
Date: Tue, 21 May 2024 12:24:15 +0100
Subject: [PATCH 2/4] :memo: Add debug messages for each group and user added
 to the LDAP tree

---
 apricot/ldap/oauth_ldap_entry.py |  3 +++
 apricot/ldap/oauth_ldap_tree.py  | 18 ++++++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/apricot/ldap/oauth_ldap_entry.py b/apricot/ldap/oauth_ldap_entry.py
index d945ef0..6845a33 100644
--- a/apricot/ldap/oauth_ldap_entry.py
+++ b/apricot/ldap/oauth_ldap_entry.py
@@ -83,3 +83,6 @@ def _bind(password: bytes) -> "OAuthLDAPEntry":
             raise LDAPInvalidCredentials(msg)
 
         return defer.maybeDeferred(_bind, password)
+
+    def list_children(self) -> "list[OAuthLDAPEntry]":
+        return [cast(OAuthLDAPEntry, entry) for entry in self._children.values()]
diff --git a/apricot/ldap/oauth_ldap_tree.py b/apricot/ldap/oauth_ldap_tree.py
index 136ce31..88333ec 100644
--- a/apricot/ldap/oauth_ldap_tree.py
+++ b/apricot/ldap/oauth_ldap_tree.py
@@ -67,15 +67,29 @@ def root(self) -> OAuthLDAPEntry:
 
             # Add groups to the groups OU
             if self.debug:
-                log.msg(f"Adding {len(oauth_adaptor.groups)} groups to the LDAP tree.")
+                log.msg(
+                    f"Attempting to add {len(oauth_adaptor.groups)} groups to the LDAP tree."
+                )
             for group_attrs in oauth_adaptor.groups:
                 groups_ou.add_child(f"CN={group_attrs.cn}", group_attrs.to_dict())
+            if self.debug:
+                children = groups_ou.list_children()
+                for child in children:
+                    log.msg(f"... {child.dn.getText()}")
+                log.msg(f"There are {len(children)} groups in the LDAP tree.")
 
             # Add users to the users OU
             if self.debug:
-                log.msg(f"Adding {len(oauth_adaptor.users)} users to the LDAP tree.")
+                log.msg(
+                    f"Attempting to add {len(oauth_adaptor.users)} users to the LDAP tree."
+                )
             for user_attrs in oauth_adaptor.users:
                 users_ou.add_child(f"CN={user_attrs.cn}", user_attrs.to_dict())
+            if self.debug:
+                children = users_ou.list_children()
+                for child in children:
+                    log.msg(f"... {child.dn.getText()}")
+                log.msg(f"There are {len(children)} users in the LDAP tree.")
 
             # Set last updated time
             log.msg("Finished building LDAP tree.")

From 801dfe516c65a7616b83dddc65b13606d7c567a9 Mon Sep 17 00:00:00 2001
From: James Robinson <james.em.robinson@gmail.com>
Date: Tue, 21 May 2024 13:48:37 +0100
Subject: [PATCH 3/4] :loud_sound: Add additional debug messages for user and
 group membership

---
 apricot/oauth/oauth_data_adaptor.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/apricot/oauth/oauth_data_adaptor.py b/apricot/oauth/oauth_data_adaptor.py
index 701e55a..2263af5 100644
--- a/apricot/oauth/oauth_data_adaptor.py
+++ b/apricot/oauth/oauth_data_adaptor.py
@@ -128,6 +128,11 @@ def _retrieve_entries(
                 for parent_dict in oauth_groups + user_primary_groups + groups_of_groups
                 if child_dn in parent_dict["member"]
             ]
+            if self.debug:
+                for group_name in child_dict["memberOf"]:
+                    log.msg(
+                        f"... user '{child_dict['cn']}' is a member of '{group_name}'"
+                    )
 
         # Ensure memberOf is set correctly for groups
         for child_dict in oauth_groups + user_primary_groups + groups_of_groups:
@@ -137,6 +142,11 @@ def _retrieve_entries(
                 for parent_dict in oauth_groups + user_primary_groups + groups_of_groups
                 if child_dn in parent_dict["member"]
             ]
+            if self.debug:
+                for group_name in child_dict["memberOf"]:
+                    log.msg(
+                        f"... group '{child_dict['cn']}' is a member of '{group_name}'"
+                    )
 
         # Annotate group and user dicts with the appropriate LDAP classes
         annotated_groups = [

From af0653abc5cf5f8f2347cf1236d5cb634b8733fe Mon Sep 17 00:00:00 2001
From: James Robinson <james.em.robinson@gmail.com>
Date: Tue, 21 May 2024 13:48:11 +0100
Subject: [PATCH 4/4] :bug: Ensure that userPrincipalName key exists before
 using it to construct group members

---
 apricot/oauth/microsoft_entra_client.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apricot/oauth/microsoft_entra_client.py b/apricot/oauth/microsoft_entra_client.py
index 7ca1e07..eecfa41 100644
--- a/apricot/oauth/microsoft_entra_client.py
+++ b/apricot/oauth/microsoft_entra_client.py
@@ -56,7 +56,7 @@ def groups(self) -> list[JSONDict]:
                 attributes["memberUid"] = [
                     str(user["userPrincipalName"]).split("@")[0]
                     for user in members["value"]
-                    if user["userPrincipalName"]
+                    if user.get("userPrincipalName")
                 ]
                 output.append(attributes)
             except KeyError as exc: