server/plugins/ldap.py (106 lines of code) (raw):

"""This is the ASF LDAP plugin for Boxer. It establishes an LDAP connection and reads project memberships. If a projects.yaml (or other override file specified) exists, it can read LDAP overrides from it for specific groups. A typical override setting would involve either an alternate LDAP base or a hardcoded list of members/owners, for instance: infrastructure: ldap: cn=infrastructure,ou=groups,ou=services,dc=apache,dc=org foundation: members: kp sk humbedooh owners: sk """ import bonsai import typing import re import yaml import os UID_RE = re.compile(r"^(?:uid=)?([^,]+)") PROJECTS_OVERRIDE = "projects.yaml" class LDAPConfig: uri: str binddn: str bindpw: str userbase: str ldapbase: str groupbase: str def __init__(self, subyaml: dict = {}): self.uri = str(subyaml.get("uri", "")) self.binddn = str(subyaml.get("binddn", "")) self.bindpw = str(subyaml.get("bindpw", "")) self.userbase = str(subyaml.get("userbase", "")) self.ldapbase = str(subyaml.get("ldapbase", "")) self.groupbase = str(subyaml.get("groupbase", "")) class LDAPClient: config: LDAPConfig client: typing.Optional[bonsai.LDAPClient] connection: typing.Optional[bonsai.LDAPConnection] ldap_override: dict def __init__(self, config: LDAPConfig, ldap_override_yaml=PROJECTS_OVERRIDE): self.config = config self.client = None self.connection = None self.ldap_override = {} if ldap_override_yaml and os.path.exists(ldap_override_yaml): try: self.ldap_override = yaml.safe_load(open(ldap_override_yaml)) except yaml.YAMLError as err: print(f"Could not load ldap override yaml, {ldap_override_yaml}: {err}") async def __aenter__(self): """Initializes an LDAP connection and returns the connection is success, None otherwise""" self.client = bonsai.LDAPClient(self.config.uri) self.client.set_credentials("SIMPLE", self.config.binddn, self.config.bindpw) self.client.set_cert_policy("allow") # TODO: Load our cert(?) # Hack around GnuTLS bug with async... - https://github.com/noirello/bonsai/issues/25 bonsai.set_connect_async(False) self.connection = await self.client.connect(is_async=True) bonsai.set_connect_async(True) return self async def get_members(self, group: str): """Async fetching of members/owners of a standard project group.""" ldap_base = self.config.groupbase % group ldap_owner_base = None members = [] owners = [] member_attr = "member" owner_attr = "owner" if self.ldap_override and group in self.ldap_override: if "ldap" in self.ldap_override[group]: ldap_base = self.ldap_override[group]["ldap"] print("Using LDAP override for group %s: %s" % (group, ldap_base)) if "ldap_owner" in self.ldap_override[group]: ldap_owner_base = self.ldap_override[group]["ldap_owner"] print("Using LDAP override for PMC group %s: %s" % (group, ldap_owner_base)) if "member_attr" in self.ldap_override[group]: member_attr = self.ldap_override[group]["member_attr"] print("Using LDAP member attribute override for group %s: %s" % (group, member_attr)) if "owner_attr" in self.ldap_override[group]: owner_attr = self.ldap_override[group]["owner_attr"] print("Using LDAP owner attribute override for group %s: %s" % (group, owner_attr)) if "members" in self.ldap_override[group]: members = self.ldap_override[group]["members"] print(f"Using membership override for group {group}: {members}") if "owners" in self.ldap_override[group]: owners = self.ldap_override[group]["owners"] print(f"Using ownership override for group {group}: {owners}") if owners and members: return owners, members try: attrs = set([member_attr, owner_attr]) assert self.connection, "LDAP Not connected" rv = await self.connection.search( ldap_base, bonsai.LDAPSearchScope.SUBTREE, None, list(attrs) ) if rv: if not members and member_attr in rv[0]: for member in rv[0][member_attr]: m = UID_RE.match(member) if m: members.append(m.group(1)) if (not ldap_owner_base) and not owners and owner_attr in rv[0]: for owner in rv[0][owner_attr]: m = UID_RE.match(owner) if m: owners.append(m.group(1)) if ldap_owner_base: rv = await self.connection.search( ldap_owner_base, bonsai.LDAPSearchScope.SUBTREE, None, [owner_attr] ) if rv: if not owners and owner_attr in rv[0]: for owner in rv[0][owner_attr]: m = UID_RE.match(owner) if m: owners.append(m.group(1)) return list(sorted(members)), list(sorted(owners)) except Exception as e: print(f"LDAP Exception for group {group}: {e}") return [], [] async def __aexit__(self, exc_type, exc_val, exc_tb): if self.connection: self.connection.close()