ldap.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. #!/usr/bin/env python3
  2. # Licensed to the Apache Software Foundation (ASF) under one
  3. # or more contributor license agreements. See the NOTICE file
  4. # distributed with this work for additional information
  5. # regarding copyright ownership. The ASF licenses this file
  6. # to you under the Apache License, Version 2.0 (the
  7. # "License"); you may not use this file except in compliance
  8. # with the License. You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing,
  13. # software distributed under the License is distributed on an
  14. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. # KIND, either express or implied. See the License for the
  16. # specific language governing permissions and limitations
  17. # under the License.
  18. """ This is the LDAP component of PyPubSub """
  19. import ldap
  20. import asyncio
  21. class LDAPConnection:
  22. def __init__(self, yml: dict):
  23. self.uri = yml.get('uri')
  24. assert isinstance(self.uri, str), "LDAP URI must be a string."
  25. self.user = yml.get('user_dn')
  26. assert isinstance(self.user, str) or self.user is None, "LDAP user DN must be a string or absent."
  27. self.base = yml.get('base_scope')
  28. assert isinstance(self.base, str), "LDAP Base scope must be a string"
  29. self.patterns: list = yml.get('membership_patterns', [])
  30. assert isinstance(self.patterns, list), "LDAP membership patterns must be a list of pattern strings"
  31. self.acl = yml.get('acl')
  32. assert isinstance(self.acl, dict), "LDAP ACL must be a dictionary (hash) of ACLs"
  33. assert ldap.initialize(self.uri)
  34. print("==== LDAP configuration looks kosher, enabling LDAP authentication as fallback ====")
  35. async def get_groups(self, user: str, password: str):
  36. """Async fetching of groups an LDAP user belongs to"""
  37. bind_dn = self.user % user # Interpolate user DN with username
  38. try:
  39. client = ldap.initialize(self.uri)
  40. client.set_option(ldap.OPT_REFERRALS, 0)
  41. client.set_option(ldap.OPT_TIMEOUT, 0)
  42. rv = client.simple_bind(bind_dn, password)
  43. while True:
  44. res = client.result(rv, timeout=0)
  45. if res and res != (None, None):
  46. break
  47. await asyncio.sleep(0.25)
  48. groups = []
  49. for role in self.patterns:
  50. rv = client.search(self.base, ldap.SCOPE_SUBTREE, role % user, ['dn'])
  51. while True:
  52. res = client.result(rv, all=0, timeout=0)
  53. if res:
  54. if res == (None, None):
  55. await asyncio.sleep(0.25)
  56. else:
  57. if not res[1]:
  58. break
  59. for tuples in res[1]:
  60. groups.append(tuples[0])
  61. else:
  62. break
  63. return groups
  64. except Exception as e:
  65. print(f"LDAP Exception for user {user}: {e}")
  66. return []