aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--arthur/apis/directory/freeipa.py7
-rw-r--r--arthur/apis/directory/ldap.py4
-rw-r--r--arthur/exts/directory/ldap.py112
3 files changed, 78 insertions, 45 deletions
diff --git a/arthur/apis/directory/freeipa.py b/arthur/apis/directory/freeipa.py
index cf4ba23..ef0f1b3 100644
--- a/arthur/apis/directory/freeipa.py
+++ b/arthur/apis/directory/freeipa.py
@@ -68,6 +68,13 @@ def deactivate_user(username: str) -> None:
client.user_mod(username, o_nsaccountlock=True)
+def activate_user(username: str) -> None:
+ """Activate a user in FreeIPA."""
+ client = create_client()
+
+ client.user_mod(username, o_nsaccountlock=False)
+
+
def create_user(username: str, display_name: str, groups: list[str], discord_id: int) -> str:
"""
Create a new user in FreeIPA. If the user exists, the password is reset and returned.
diff --git a/arthur/apis/directory/ldap.py b/arthur/apis/directory/ldap.py
index c919f55..7af4c08 100644
--- a/arthur/apis/directory/ldap.py
+++ b/arthur/apis/directory/ldap.py
@@ -22,6 +22,7 @@ class LDAPUser:
employee_number: str | None = None
display_name: str | None = None
groups: list[str] | None = None
+ locked: bool = False
def prepare_dn(dn: str) -> str:
@@ -60,7 +61,7 @@ async def find_users() -> list[LDAPUser]:
prepare_dn("cn=users,cn=accounts"),
LDAPSearchScope.SUBTREE,
"(mail=*@pydis.wtf)",
- ["uid", "employeeNumber", "displayName", "memberOf"],
+ ["uid", "employeeNumber", "displayName", "memberOf", "nsAccountLock"],
)
for user in users:
@@ -77,6 +78,7 @@ async def find_users() -> list[LDAPUser]:
employee_number=user.get("employeeNumber", [None])[0],
display_name=user["displayName"][0],
groups=parsed_groups,
+ locked=user.get("nsAccountLock", [False])[0],
)
found_users.append(new_user)
diff --git a/arthur/exts/directory/ldap.py b/arthur/exts/directory/ldap.py
index 366a42e..72cb460 100644
--- a/arthur/exts/directory/ldap.py
+++ b/arthur/exts/directory/ldap.py
@@ -89,6 +89,7 @@ class LDAPSyncAction(StrEnum):
REMOVE = "remove"
KEEP = "keep"
CHANGE = "change"
+ NO_ACTION = "no_action"
@dataclass
@@ -154,58 +155,72 @@ class LDAP(commands.Cog):
@tasks.loop(minutes=10)
async def sync_users(self) -> None:
"""Sync users with the LDAP directory."""
- logger.info("Syncing users with the LDAP directory.")
+ try:
+ logger.info("Syncing users with the LDAP directory.")
- diff, missing_emp, counts = await self.get_user_diff()
-
- add_users = counts[LDAPSyncAction.ADD]
- remove_users = counts[LDAPSyncAction.REMOVE]
- keep_users = counts[LDAPSyncAction.KEEP]
- change_users = counts[LDAPSyncAction.CHANGE]
+ diff, missing_emp, counts = await self.get_user_diff()
- logger.info(
- f"LDAP: {add_users} missing users, removing {remove_users} users, "
- f"keeping {keep_users} users, and changing {change_users} users."
- )
+ add_users = counts[LDAPSyncAction.ADD]
+ remove_users = counts[LDAPSyncAction.REMOVE]
+ keep_users = counts[LDAPSyncAction.KEEP]
+ change_users = counts[LDAPSyncAction.CHANGE]
+ no_action_users = counts[LDAPSyncAction.NO_ACTION]
- if len(missing_emp) > 0:
- logger.error(
- "LDAP: Some users are missing an employee number. This may lead to duplicated users being created."
+ logger.info(
+ f"LDAP: {add_users} missing users, removing {remove_users} users, "
+ f"keeping {keep_users} users, changing {change_users} users and no action for {no_action_users} users."
)
- await self.bot.get_channel(CONFIG.devops_channel_id).send(
- ":x: LDAP Sync: Some users are missing an employee number. This may lead to duplicate users, please rectify."
- )
+ if len(missing_emp) > 0:
+ logger.error(
+ "LDAP: Some users are missing an employee number. This may lead to duplicated users being created."
+ )
+
+ await self.bot.get_channel(CONFIG.devops_channel_id).send(
+ ":x: LDAP Sync: Some users are missing an employee number. This may lead to duplicate users, please rectify."
+ )
- notified_users = []
+ notified_users = []
- async for message in self.bot.get_channel(CONFIG.ldap_bootstrap_channel_id).history(
- limit=None, oldest_first=True
- ):
- if (
- "Python Discord LDAP enrollment" in message.content
- or len(message.mentions) == 0
- or message.author != self.bot.user
+ async for message in self.bot.get_channel(CONFIG.ldap_bootstrap_channel_id).history(
+ limit=None, oldest_first=True
):
- continue
+ if (
+ "Python Discord LDAP enrollment" in message.content
+ or len(message.mentions) == 0
+ or message.author != self.bot.user
+ ):
+ continue
- notified_users.append(message.mentions[0])
+ notified_users.append(message.mentions[0])
- for user in diff:
- if user.action == LDAPSyncAction.ADD:
- if user.discord_user in notified_users:
- continue
+ for user in diff:
+ await self._process_user(user, notified_users)
- if NOTIFICATIONS_ENABLED:
- await self.bot.get_channel(CONFIG.ldap_bootstrap_channel_id).send(
- ELIGIBLE_MESSAGE.format(mention=user.discord_user.mention)
- )
- if user.action == LDAPSyncAction.REMOVE:
- freeipa.deactivate_user(user.ldap_user.uid)
- elif user.action == LDAPSyncAction.CHANGE:
- freeipa.set_user_groups(user.ldap_user.uid, user.groups)
+ logger.info("LDAP: Sync complete.")
+ except Exception as e: # noqa: BLE001
+ logger.exception(f"LDAP: Error during sync: {e}", exc_info=True)
+ await self.bot.get_channel(CONFIG.devops_channel_id).send(
+ f":x: LDAP Sync Error: ```python\n{e}```"
+ )
+
+ async def _process_user(self, user: DiffedUser, notified_users: list[discord.User]) -> None:
+ if user.action == LDAPSyncAction.ADD:
+ if user.discord_user in notified_users:
+ return
- logger.info("LDAP: Sync complete.")
+ if NOTIFICATIONS_ENABLED:
+ await self.bot.get_channel(CONFIG.ldap_bootstrap_channel_id).send(
+ ELIGIBLE_MESSAGE.format(mention=user.discord_user.mention)
+ )
+ if user.action == LDAPSyncAction.KEEP:
+ if user.ldap_user and user.ldap_user.locked:
+ freeipa.activate_user(user.ldap_user.uid)
+ if user.action == LDAPSyncAction.REMOVE:
+ if user.ldap_user and not user.ldap_user.locked:
+ freeipa.deactivate_user(user.ldap_user.uid)
+ elif user.action == LDAPSyncAction.CHANGE:
+ freeipa.set_user_groups(user.ldap_user.uid, user.groups)
async def cleanup_bootstrap(self, user: discord.Member) -> None:
"""Clear up the bootstrap message for a user."""
@@ -346,9 +361,12 @@ class LDAP(commands.Cog):
if base_role not in user.roles:
if user.id in ldap_discord_id_map:
- diff.append(
- DiffedUser(user, ldap_discord_id_map[user.id], [], LDAPSyncAction.REMOVE)
+ action = (
+ LDAPSyncAction.NO_ACTION
+ if ldap_discord_id_map[user.id].locked
+ else LDAPSyncAction.REMOVE
)
+ diff.append(DiffedUser(user, ldap_discord_id_map[user.id], [], action))
continue
user_role_ids = {r.id for r in user.roles}
@@ -364,9 +382,12 @@ class LDAP(commands.Cog):
else:
diff.append(DiffedUser(user, None, roles, LDAPSyncAction.ADD))
elif user.id in ldap_discord_id_map:
- diff.append(
- DiffedUser(user, ldap_discord_id_map[user.id], [], LDAPSyncAction.REMOVE)
+ action = (
+ LDAPSyncAction.NO_ACTION
+ if ldap_discord_id_map[user.id].locked
+ else LDAPSyncAction.REMOVE
)
+ diff.append(DiffedUser(user, ldap_discord_id_map[user.id], [], action))
counter = Counter([user.action for user in diff])
@@ -389,6 +410,7 @@ class LDAP(commands.Cog):
remove_users = counts[LDAPSyncAction.REMOVE]
keep_users = counts[LDAPSyncAction.KEEP]
change_users = counts[LDAPSyncAction.CHANGE]
+ no_action_users = counts[LDAPSyncAction.NO_ACTION]
diff_message = "# LDAP Sync Overview\n"
@@ -396,6 +418,7 @@ class LDAP(commands.Cog):
diff_message += f"**Removing Users:** {remove_users}\n"
diff_message += f"**Keeping Users:** {keep_users}\n"
diff_message += f"**Changing Users:** {change_users}\n"
+ diff_message += f"**No Action Required:** {no_action_users}\n\n"
diff_message += "```diff\n"
@@ -406,6 +429,7 @@ class LDAP(commands.Cog):
LDAPSyncAction.REMOVE: "-",
LDAPSyncAction.KEEP: " ",
LDAPSyncAction.CHANGE: "~",
+ LDAPSyncAction.NO_ACTION: "#",
}
messages = [diff_message]