diff options
Diffstat (limited to 'ipaserver/plugins/ldap2.py')
-rw-r--r-- | ipaserver/plugins/ldap2.py | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index 048e2c510..e62f74b90 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -56,6 +56,11 @@ from ipalib import api, errors from ipalib.crud import CrudBackend from ipalib.request import context +# Group Member types +MEMBERS_ALL = 0 +MEMBERS_DIRECT = 1 +MEMBERS_INDIRECT = 2 + class ldap2(LDAPClient, CrudBackend): """ @@ -176,6 +181,205 @@ class ldap2(LDAPClient, CrudBackend): # ignore when trying to unbind multiple times pass + def find_entries(self, filter=None, attrs_list=None, base_dn=None, + scope=_ldap.SCOPE_SUBTREE, time_limit=None, + size_limit=None, search_refs=False): + if time_limit is None or size_limit is None: + config = self.get_ipa_config() + if time_limit is None: + time_limit = config.get('ipasearchtimelimit', [None])[0] + if size_limit is None: + size_limit = config.get('ipasearchrecordslimit', [None])[0] + + res, truncated = super(ldap2, self).find_entries( + filter=filter, attrs_list=attrs_list, base_dn=base_dn, scope=scope, + time_limit=time_limit, size_limit=size_limit, + search_refs=search_refs) + + if attrs_list and ( + 'memberindirect' in attrs_list or '*' in attrs_list): + for r in res: + if not 'member' in r[1]: + continue + else: + members = r[1]['member'] + indirect = self.get_members( + r[0], members, membertype=MEMBERS_INDIRECT, + time_limit=time_limit, size_limit=size_limit) + if len(indirect) > 0: + r[1]['memberindirect'] = indirect + if attrs_list and ( + 'memberofindirect' in attrs_list or '*' in attrs_list): + for r in res: + if 'memberof' in r[1]: + memberof = r[1]['memberof'] + del r[1]['memberof'] + elif 'memberOf' in r[1]: + memberof = r[1]['memberOf'] + del r[1]['memberOf'] + else: + continue + direct, indirect = self.get_memberof( + r[0], memberof, time_limit=time_limit, + size_limit=size_limit) + if len(direct) > 0: + r[1]['memberof'] = direct + if len(indirect) > 0: + r[1]['memberofindirect'] = indirect + + return (res, truncated) + + def get_members(self, group_dn, members, attr_list=[], + membertype=MEMBERS_ALL, time_limit=None, size_limit=None): + """Do a memberOf search of groupdn and return the attributes in + attr_list (an empty list returns all attributes). + + membertype = MEMBERS_ALL all members returned + membertype = MEMBERS_DIRECT only direct members are returned + membertype = MEMBERS_INDIRECT only inherited members are returned + + Members may be included in a group as a result of being a member + of a group that is a member of the group being queried. + + Returns a list of DNs. + """ + + assert isinstance(group_dn, DN) + + if membertype not in [MEMBERS_ALL, MEMBERS_DIRECT, MEMBERS_INDIRECT]: + return None + + self.log.debug( + "get_members: group_dn=%s members=%s membertype=%s", + group_dn, members, membertype) + search_group_dn = ldap.filter.escape_filter_chars(str(group_dn)) + searchfilter = "(memberof=%s)" % search_group_dn + + attr_list.append("member") + + # Verify group membership + + results = [] + if membertype == MEMBERS_ALL or membertype == MEMBERS_INDIRECT: + api = self.get_api() + if api: + user_container_dn = DN(api.env.container_user, api.env.basedn) + host_container_dn = DN(api.env.container_host, api.env.basedn) + else: + user_container_dn = host_container_dn = None + checkmembers = set(DN(x) for x in members) + checked = set() + while checkmembers: + member_dn = checkmembers.pop() + checked.add(member_dn) + + # No need to check entry types that are not nested for + # additional members + if user_container_dn and ( + member_dn.endswith(user_container_dn) or + member_dn.endswith(host_container_dn)): + results.append([member_dn, {}]) + continue + try: + result, truncated = self.find_entries( + searchfilter, attr_list, member_dn, + time_limit=time_limit, size_limit=size_limit, + scope=ldap.SCOPE_BASE) + if truncated: + raise errors.LimitsExceeded() + results.append(list(result[0])) + for m in result[0][1].get('member', []): + # This member may contain other members, add it to our + # candidate list + if m not in checked: + checkmembers.add(m) + except errors.NotFound: + pass + + if membertype == MEMBERS_ALL: + entries = [] + for e in results: + entries.append(e[0]) + + return entries + + dn, group = self.get_entry( + group_dn, ['member'], + size_limit=size_limit, time_limit=time_limit) + real_members = group.get('member', []) + + entries = [] + for e in results: + if e[0] not in real_members and e[0] not in entries: + if membertype == MEMBERS_INDIRECT: + entries.append(e[0]) + else: + if membertype == MEMBERS_DIRECT: + entries.append(e[0]) + + self.log.debug("get_members: result=%s", entries) + return entries + + def get_memberof(self, entry_dn, memberof, time_limit=None, + size_limit=None): + """ + Examine the objects that an entry is a member of and determine if they + are a direct or indirect member of that group. + + entry_dn: dn of the entry we want the direct/indirect members of + memberof: the memberOf attribute for entry_dn + + Returns two memberof lists: (direct, indirect) + """ + + assert isinstance(entry_dn, DN) + + self.log.debug( + "get_memberof: entry_dn=%s memberof=%s", entry_dn, memberof) + if not type(memberof) in (list, tuple): + return ([], []) + if len(memberof) == 0: + return ([], []) + + search_entry_dn = ldap.filter.escape_filter_chars(str(entry_dn)) + attr_list = ["memberof"] + searchfilter = "(|(member=%s)(memberhost=%s)(memberuser=%s))" % ( + search_entry_dn, search_entry_dn, search_entry_dn) + + # Search only the groups for which the object is a member to + # determine if it is directly or indirectly associated. + + results = [] + for group in memberof: + assert isinstance(group, DN) + try: + result, truncated = self.find_entries( + searchfilter, attr_list, + group, time_limit=time_limit, size_limit=size_limit, + scope=ldap.SCOPE_BASE) + results.extend(list(result)) + except errors.NotFound: + pass + + direct = [] + # If there is an exception here, it is likely due to a failure in + # referential integrity. All members should have corresponding + # memberOf entries. + indirect = list(memberof) + for r in results: + direct.append(r[0]) + try: + indirect.remove(r[0]) + except ValueError, e: + self.log.info( + 'Failed to remove indirect entry %s from %s', + r[0], entry_dn) + raise e + + self.log.debug( + "get_memberof: result direct=%s indirect=%s", direct, indirect) + return (direct, indirect) + config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]} def get_ipa_config(self, attrs_list=None): """Returns the IPA configuration entry (dn, entry_attrs).""" |