mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Move IPA specific code from LDAPClient to the ldap2 plugin.
https://fedorahosted.org/freeipa/ticket/3971
This commit is contained in:
committed by
Petr Viktorin
parent
73b8047b22
commit
73df6150e5
@@ -47,11 +47,6 @@ DEFAULT_TIMEOUT = 10
|
||||
DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12'
|
||||
_debug_log_ldap = False
|
||||
|
||||
# Group Member types
|
||||
MEMBERS_ALL = 0
|
||||
MEMBERS_DIRECT = 1
|
||||
MEMBERS_INDIRECT = 2
|
||||
|
||||
_missing = object()
|
||||
|
||||
|
||||
@@ -1086,13 +1081,6 @@ class LDAPClient(object):
|
||||
def _init_connection(self):
|
||||
self.conn = None
|
||||
|
||||
def get_api(self):
|
||||
"""Return the API if available, otherwise None
|
||||
|
||||
May be overridden in a subclass.
|
||||
"""
|
||||
return None
|
||||
|
||||
@contextlib.contextmanager
|
||||
def error_handler(self, arg_desc=None):
|
||||
"""Context manager that handles LDAPErrors
|
||||
@@ -1398,9 +1386,9 @@ class LDAPClient(object):
|
||||
attrs_list -- list of attributes to return, all if None (default None)
|
||||
base_dn -- dn of the entry at which to start the search (default '')
|
||||
scope -- search scope, see LDAP docs (default ldap2.SCOPE_SUBTREE)
|
||||
time_limit -- time limit in seconds (default use IPA config values)
|
||||
time_limit -- time limit in seconds (default unlimited)
|
||||
size_limit -- size (number of entries returned) limit
|
||||
(default use IPA config values)
|
||||
(default unlimited)
|
||||
search_refs -- allow search references to be returned
|
||||
(default skips these entries)
|
||||
"""
|
||||
@@ -1412,21 +1400,17 @@ class LDAPClient(object):
|
||||
res = []
|
||||
truncated = 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', [-1])[0]
|
||||
if time_limit is None or time_limit == 0:
|
||||
time_limit = -1.0
|
||||
if size_limit is None:
|
||||
size_limit = config.get('ipasearchrecordslimit', [0])[0]
|
||||
if time_limit == 0:
|
||||
time_limit = -1
|
||||
size_limit = 0
|
||||
if not isinstance(size_limit, int):
|
||||
size_limit = int(size_limit)
|
||||
if not isinstance(time_limit, float):
|
||||
time_limit = float(time_limit)
|
||||
|
||||
if attrs_list:
|
||||
attrs_list = list(set(attrs_list))
|
||||
attrs_list = [a.lower() for a in set(attrs_list)]
|
||||
|
||||
# pass arguments to python-ldap
|
||||
with self.error_handler():
|
||||
@@ -1450,37 +1434,6 @@ class LDAPClient(object):
|
||||
if not res and not truncated:
|
||||
raise errors.NotFound(reason='no such entry')
|
||||
|
||||
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 find_entry_by_attr(self, attr, value, object_class, attrs_list=None,
|
||||
@@ -1529,164 +1482,6 @@ class LDAPClient(object):
|
||||
raise errors.LimitsExceeded()
|
||||
return entry[0]
|
||||
|
||||
def get_ipa_config(self, attrs_list=None):
|
||||
"""Returns the IPA configuration entry.
|
||||
|
||||
Overriden in the subclasses that have access to IPA configuration.
|
||||
"""
|
||||
return {}
|
||||
|
||||
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)
|
||||
|
||||
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_dn_and_attrs(self, entry_or_dn, entry_attrs):
|
||||
"""Helper for legacy calling style for {add,update}_entry
|
||||
"""
|
||||
|
||||
@@ -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)."""
|
||||
|
||||
Reference in New Issue
Block a user