Skip to content
Snippets Groups Projects
syncldapmemberships.py 4.56 KiB
Newer Older
nd's avatar
nd committed
import logging
nd's avatar
nd committed
import ldap
nd's avatar
nd committed

nd's avatar
nd committed
from django.contrib.auth import get_user_model, load_backend
nd's avatar
nd committed
from django.core.management.base import BaseCommand
nd's avatar
nd committed
from django_auth_ldap.config import LDAPSearch
nd's avatar
nd committed
import django.conf

from urllib.error import HTTPError

nd's avatar
nd committed
from postorius.models import List, MailmanUser
from postorius.utils import get_mailman_client
nd's avatar
nd committed

nd's avatar
nd committed
logger = logging.getLogger(__name__)
nd's avatar
nd committed

class Command(BaseCommand):
	can_import_settings = True
	help = 'Synchronize mailing list memberships from a LDAP server'

	def handle(self, *args, **options):
		ldap_backend = load_backend('django_auth_ldap.backend.LDAPBackend')
		conn = ldap_backend.ldap.initialize(ldap_backend.settings.SERVER_URI, bytes_mode=False)
		for opt, value in ldap_backend.settings.CONNECTION_OPTIONS.items():
			conn.set_option(opt, value)
		if ldap_backend.settings.START_TLS:
			conn.start_tls_s()
		conn.simple_bind_s(ldap_backend.settings.BIND_DN, ldap_backend.settings.BIND_PASSWORD)

		client = get_mailman_client()

		user_search = django.conf.settings.AUTH_LDAP_USER_SEARCH_ALL_NAME
		results = user_search.execute(conn)
		ldap_users = [list(attr.values())[0][0] for dn, attr in results ]
		django_users = get_user_model().objects.all()
		backref_mapping = {'member': 'members', 'moderator': 'moderators', 'owner': 'owners'}
nd's avatar
nd committed
		mm_users = {}

		membership_settings = getattr(django.conf.settings, 'LDAP_MEMBERSHIP_SYNC', {})

		# create all django user in mailman
		for user in django_users:
			mm_users[user.username] = MailmanUser.objects.get_or_create_from_django(user)
			if mm_users[user.username].display_name != user.get_full_name():
nd's avatar
nd committed
				logger.warning("update display_name on {} to {}".format(user.username, user.get_full_name()))
nd's avatar
nd committed
				mm_users[user.username].display_name = user.get_full_name()
				mm_users[user.username].save()

		mailman_id2username = {mm_users[i].user_id: i for i in mm_users}

nd's avatar
nd committed
		for list_name in membership_settings:
			ldap_setting = membership_settings[list_name].get('ldap', {})
			for membership_type in ldap_setting:
				if not ldap_setting[membership_type].get('enabled'):
					continue
				ldap_members = LDAPSearch(
						ldap_setting[membership_type]['dn'], ldap.SCOPE_SUBTREE,
						ldap_setting[membership_type]['filter'],
						[ldap_setting[membership_type]['username_attr']]).execute(conn)
				ldap_member_names = [list(attr.values())[0][0] for dn, attr in ldap_members ]

				# we refetch the mm_list each time because of wired caching problems
				mm_list = client.get_list(list_name)

				mm_members = getattr(mm_list, backref_mapping[membership_type], [])
				for mm_member in mm_members:
					try:
						username = mailman_id2username.get(mm_member.user.user_id, None)
						if not username or not username in ldap_member_names:
							# user should not be subscribed -> remove
nd's avatar
nd committed
							logger.warning("remove {} ( {} ) as {} on {}".format(username, mm_member.user, membership_type, list_name))
nd's avatar
nd committed
							mm_member.unsubscribe()
					except Exception as e:
						logger.exception(e)
						continue
nd's avatar
nd committed

nd's avatar
nd committed
				for username in mm_users:
					if not username in ldap_member_names:
						continue
					try:
						mm_user = mm_users[username]
						displayname = mm_users[username].display_name or username
						try:
							# user might not be subscribed but should be subscribed -> subscribe
							# we do not test if he is subscibed already because the mailman api is inconsistent and reports wrong state information for this...
							# instead we always try to subscribe and catch the error if we are subscribed already
							for address in mm_user.addresses[0]:
								user_mail = address.email
								if membership_type == 'member':
									# discard a subscription request before trying to subscribe else it will fail
									for request in mm_list.requests:
										if request['email'] == user_mail:
											mm_list.discard_request(request['token'])
									mm_list.subscribe(user_mail,
											display_name=displayname,
											pre_verified=True,
											pre_confirmed=True,
											pre_approved=True)
								elif membership_type == 'moderator':
									mm_list.add_moderator(user_mail,
											display_name=displayname)
								elif membership_type == 'owner':
									mm_list.add_moderator(user_mail,
											display_name=displayname)
								logger.warning("subscribe {} ( {} ) as {} on {}".format(username, user_mail, membership_type, list_name))
						except HTTPError as e:
							# We get a http error if the user is already subscribed.
							# It is silently ignored
							if e.code == 409 and e.reason == b'Member already subscribed':
								continue
nd's avatar
nd committed
					except Exception as e:
						logger.exception(e)
						continue