cmsmanage/membershipworks/tasks/ucsAccounts.py

134 lines
4.6 KiB
Python

import asyncio
import random
import re
import string
from django.conf import settings
from udm_rest_client.udm import UDM
from udm_rest_client.exceptions import NoObject, UdmError
from membershipworks.models import Member, Flag
USER_BASE = "cn=users,dc=sawtooth,dc=claremontmakerspace,dc=org"
GROUP_BASE = "cn=groups,dc=sawtooth,dc=claremontmakerspace,dc=org"
RAND_PW_LEN = 20
GROUPS_REGEX = "|".join(
["Certified: .*", "Access .*\\?", "CMS .*", "Volunteer: .*", "Database .*"]
)
# From an API error message:
# A group name must start and end with a letter, number or underscore.
# In between additionally spaces, dashes and dots are allowed.
def sanitize_group_name(name):
sanitized_body = re.sub(r"[^0-9A-Za-z_ -.]+", ".", name)
sanitized_start_end = re.sub("^[^0-9A-Za-z_]|[^0-9A-Za-z_]$", "_", sanitized_body)
return "MW_" + sanitized_start_end
# From an API error message: "Username must only contain numbers, letters and dots!"
def sanitize_user_name(name):
return re.sub(r"[^0-9a-z.]", ".", name.lower()).strip(".")
async def make_groups(udm):
group_mod = udm.get("groups/group")
existing_groups = {g.props.name async for g in group_mod.search()}
flag_groups = {
sanitize_group_name(flag.name)
async for flag in Flag.objects.filter(
type__in=["label", "folder"], name__regex=GROUPS_REGEX
)
}
missing_groups = flag_groups - existing_groups
print(f"Creating missing groups: {missing_groups}")
for group_name in missing_groups:
group = await group_mod.new()
group.props.name = group_name
await group.save()
async def sync_member(user_mod, member: Member):
username = sanitize_user_name(member.account_name)
try: # try to get an existing user to update
user = await user_mod.get(f"uid={username},{USER_BASE}")
except NoObject: # create a new user
print("Creating new user {username}")
# TODO: search by employeeNumber and rename users when needed
user = await user_mod.new()
# set a random password and ensure it is changed at next login
user.props.password = "".join(
random.choice(string.ascii_letters + string.digits)
for x in range(0, RAND_PW_LEN)
)
user.props.pwdChangeNextLogin = True
user.props.update(
{
"title": "", # Title
"firstname": member.first_name,
"lastname": member.last_name, # (c)
"username": username, # (cmr)
"description": "", # Description
# "password": "", # (c) Password
# "mailPrimaryAddress": member["Email"], # Primary e-mail address
# "displayName": "", # Display name
# "birthday": "", # Birthdate
# "jpegPhoto": "", # Picture of the user (JPEG format)
"employeeNumber": member.uid,
# "employeeType": "", # Employee type
"homedrive": "H:", # Windows home drive
"sambahome": f"\\\\ucs\\{username}", # Windows home path
"profilepath": "%LOGONSERVER%\\%USERNAME%\\windows-profiles\\default", # Windows profile directory
"disabled": not member.is_active,
# "userexpiry": member["Renewal Date"],
# "pwdChangeNextLogin": "1", # User has to change password on next login
# "sambaLogonHours": "", # Permitted times for Windows logins
"e-mail": [member.email], # ([]) E-mail address
"phone": [member.phone], # Telephone number
# "PasswordRecoveryMobile": member["Phone"], # Mobile phone number
"PasswordRecoveryEmail": member.email,
}
)
new_groups = [
f"cn={sanitize_group_name(flag.name)},{GROUP_BASE}"
async for flag in member.flags.filter(
type__in=["label", "folder"], name__regex=GROUPS_REGEX
)
]
# groups not from this script
other_old_groups = [g for g in user.props.groups if not g[3:].startswith("MW_")]
user.props.groups = other_old_groups + new_groups
try:
await user.save()
except UdmError:
print("Failed to save user", username)
print(user.props)
raise
async def async_accounts():
async with UDM(**settings.UCS) as udm:
print("Syncing groups")
await make_groups(udm)
print("Syncing members")
user_mod = udm.get("users/user")
async for member in Member.objects.with_is_active().filter(
Member.objects.has_flag("folder", "Members")
| Member.objects.has_flag("folder", "CMS Staff")
):
await sync_member(user_mod, member)
def sync_accounts():
asyncio.run(async_accounts())