mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Move entry finding methods to LDAPConnection
The get_ipa_config method relies on IPA being installed. Leave the implementation in ldap2, and add stub get_ipa_config that simply returns an empty dictionary to LDAPConnection. The get_members method contains an optimization that also relies on the api object. The optimization is disabled in base LDAPConfig. Part of the work for: https://fedorahosted.org/freeipa/ticket/2660
This commit is contained in:
parent
ddee3aa900
commit
0c26913309
@ -51,6 +51,12 @@ DEFAULT_TIMEOUT = 10
|
|||||||
DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12'
|
DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12'
|
||||||
_debug_log_ldap = False
|
_debug_log_ldap = False
|
||||||
|
|
||||||
|
# Group Member types
|
||||||
|
MEMBERS_ALL = 0
|
||||||
|
MEMBERS_DIRECT = 1
|
||||||
|
MEMBERS_INDIRECT = 2
|
||||||
|
|
||||||
|
|
||||||
def unicode_from_utf8(val):
|
def unicode_from_utf8(val):
|
||||||
'''
|
'''
|
||||||
val is a UTF-8 encoded string, return a unicode object.
|
val is a UTF-8 encoded string, return a unicode object.
|
||||||
@ -801,6 +807,13 @@ class LDAPConnection(object):
|
|||||||
def _init_connection(self):
|
def _init_connection(self):
|
||||||
self.conn = None
|
self.conn = None
|
||||||
|
|
||||||
|
def get_api(self):
|
||||||
|
"""Return the API if available, otherwise None
|
||||||
|
|
||||||
|
May be overridden in a subclass.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
def handle_errors(self, e, arg_desc=None):
|
def handle_errors(self, e, arg_desc=None):
|
||||||
"""Universal LDAPError handler
|
"""Universal LDAPError handler
|
||||||
|
|
||||||
@ -1078,6 +1091,314 @@ class LDAPConnection(object):
|
|||||||
)
|
)
|
||||||
return self.combine_filters(flts, rules)
|
return self.combine_filters(flts, rules)
|
||||||
|
|
||||||
|
def find_entries(self, filter=None, attrs_list=None, base_dn=None,
|
||||||
|
scope=_ldap.SCOPE_SUBTREE, time_limit=None,
|
||||||
|
size_limit=None, normalize=True, search_refs=False):
|
||||||
|
"""
|
||||||
|
Return a list of entries and indication of whether the results were
|
||||||
|
truncated ([(dn, entry_attrs)], truncated) matching specified search
|
||||||
|
parameters followed by truncated flag. If the truncated flag is True,
|
||||||
|
search hit a server limit and its results are incomplete.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
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)
|
||||||
|
size_limit -- size (number of entries returned) limit
|
||||||
|
(default use IPA config values)
|
||||||
|
normalize -- normalize the DN (default True)
|
||||||
|
search_refs -- allow search references to be returned
|
||||||
|
(default skips these entries)
|
||||||
|
"""
|
||||||
|
if base_dn is None:
|
||||||
|
base_dn = DN()
|
||||||
|
assert isinstance(base_dn, DN)
|
||||||
|
if normalize:
|
||||||
|
base_dn = self.normalize_dn(base_dn)
|
||||||
|
if not filter:
|
||||||
|
filter = '(objectClass=*)'
|
||||||
|
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 size_limit is None:
|
||||||
|
size_limit = config.get('ipasearchrecordslimit', [0])[0]
|
||||||
|
if time_limit == 0:
|
||||||
|
time_limit = -1
|
||||||
|
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))
|
||||||
|
|
||||||
|
# pass arguments to python-ldap
|
||||||
|
try:
|
||||||
|
id = self.conn.search_ext(
|
||||||
|
base_dn, scope, filter, attrs_list, timeout=time_limit,
|
||||||
|
sizelimit=size_limit
|
||||||
|
)
|
||||||
|
while True:
|
||||||
|
(objtype, res_list) = self.conn.result(id, 0)
|
||||||
|
if not res_list:
|
||||||
|
break
|
||||||
|
if (objtype == _ldap.RES_SEARCH_ENTRY or
|
||||||
|
(search_refs and
|
||||||
|
objtype == _ldap.RES_SEARCH_REFERENCE)):
|
||||||
|
res.append(res_list[0])
|
||||||
|
except (_ldap.ADMINLIMIT_EXCEEDED, _ldap.TIMELIMIT_EXCEEDED,
|
||||||
|
_ldap.SIZELIMIT_EXCEEDED), e:
|
||||||
|
truncated = True
|
||||||
|
except _ldap.LDAPError, e:
|
||||||
|
self.handle_errors(e)
|
||||||
|
|
||||||
|
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,
|
||||||
|
normalize=normalize)
|
||||||
|
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, normalize=normalize)
|
||||||
|
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,
|
||||||
|
base_dn=None):
|
||||||
|
"""
|
||||||
|
Find entry (dn, entry_attrs) by attribute and object class.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
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 '')
|
||||||
|
"""
|
||||||
|
|
||||||
|
if base_dn is None:
|
||||||
|
base_dn = DN()
|
||||||
|
assert isinstance(base_dn, DN)
|
||||||
|
|
||||||
|
search_kw = {attr: value, 'objectClass': object_class}
|
||||||
|
filter = self.make_filter(search_kw, rules=self.MATCH_ALL)
|
||||||
|
(entries, truncated) = self.find_entries(filter, attrs_list, base_dn)
|
||||||
|
|
||||||
|
if len(entries) > 1:
|
||||||
|
raise errors.SingleMatchExpected(found=len(entries))
|
||||||
|
else:
|
||||||
|
if truncated:
|
||||||
|
raise errors.LimitsExceeded()
|
||||||
|
else:
|
||||||
|
return entries[0]
|
||||||
|
|
||||||
|
def get_entry(self, dn, attrs_list=None, time_limit=None,
|
||||||
|
size_limit=None, normalize=True):
|
||||||
|
"""
|
||||||
|
Get entry (dn, entry_attrs) by dn.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
attrs_list - list of attributes to return, all if None (default None)
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert isinstance(dn, DN)
|
||||||
|
|
||||||
|
(entry, truncated) = self.find_entries(
|
||||||
|
None, attrs_list, dn, self.SCOPE_BASE, time_limit=time_limit,
|
||||||
|
size_limit=size_limit, normalize=normalize
|
||||||
|
)
|
||||||
|
|
||||||
|
if truncated:
|
||||||
|
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, normalize=True):
|
||||||
|
"""
|
||||||
|
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 = ["dn", "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, normalize=normalize)
|
||||||
|
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,
|
||||||
|
normalize=True):
|
||||||
|
"""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, normalize=normalize)
|
||||||
|
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, ['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
|
||||||
|
|
||||||
|
|
||||||
class IPAdmin(LDAPConnection):
|
class IPAdmin(LDAPConnection):
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ import pwd
|
|||||||
|
|
||||||
import krbV
|
import krbV
|
||||||
import ldap as _ldap
|
import ldap as _ldap
|
||||||
import ldap.filter as _ldap_filter
|
|
||||||
|
|
||||||
from ipapython.dn import DN, RDN
|
from ipapython.dn import DN, RDN
|
||||||
from ipaserver.ipaldap import (
|
from ipaserver.ipaldap import (
|
||||||
@ -60,11 +59,6 @@ from ipalib import api, errors
|
|||||||
from ipalib.crud import CrudBackend
|
from ipalib.crud import CrudBackend
|
||||||
from ipalib.request import context
|
from ipalib.request import context
|
||||||
|
|
||||||
# Group Member types
|
|
||||||
MEMBERS_ALL = 0
|
|
||||||
MEMBERS_DIRECT = 1
|
|
||||||
MEMBERS_INDIRECT = 2
|
|
||||||
|
|
||||||
|
|
||||||
class ldap2(LDAPConnection, CrudBackend):
|
class ldap2(LDAPConnection, CrudBackend):
|
||||||
"""
|
"""
|
||||||
@ -97,6 +91,9 @@ class ldap2(LDAPConnection, CrudBackend):
|
|||||||
# do not set it
|
# do not set it
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_api(self):
|
||||||
|
return api
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.isconnected():
|
if self.isconnected():
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
@ -225,146 +222,6 @@ class ldap2(LDAPConnection, CrudBackend):
|
|||||||
except _ldap.LDAPError, e:
|
except _ldap.LDAPError, e:
|
||||||
self.handle_errors(e)
|
self.handle_errors(e)
|
||||||
|
|
||||||
def find_entries(self, filter=None, attrs_list=None, base_dn=None,
|
|
||||||
scope=_ldap.SCOPE_SUBTREE, time_limit=None, size_limit=None,
|
|
||||||
normalize=True, search_refs=False):
|
|
||||||
"""
|
|
||||||
Return a list of entries and indication of whether the results were
|
|
||||||
truncated ([(dn, entry_attrs)], truncated) matching specified search
|
|
||||||
parameters followed by truncated flag. If the truncated flag is True,
|
|
||||||
search hit a server limit and its results are incomplete.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
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)
|
|
||||||
size_limit -- size (number of entries returned) limit (default use IPA config values)
|
|
||||||
normalize -- normalize the DN (default True)
|
|
||||||
search_refs -- allow search references to be returned (default skips these entries)
|
|
||||||
"""
|
|
||||||
if base_dn is None:
|
|
||||||
base_dn = DN()
|
|
||||||
assert isinstance(base_dn, DN)
|
|
||||||
if normalize:
|
|
||||||
base_dn = self.normalize_dn(base_dn)
|
|
||||||
if not filter:
|
|
||||||
filter = '(objectClass=*)'
|
|
||||||
res = []
|
|
||||||
truncated = False
|
|
||||||
|
|
||||||
if time_limit is None or size_limit is None:
|
|
||||||
(cdn, config) = self.get_ipa_config()
|
|
||||||
if time_limit is None:
|
|
||||||
time_limit = config.get('ipasearchtimelimit', [-1])[0]
|
|
||||||
if size_limit is None:
|
|
||||||
size_limit = config.get('ipasearchrecordslimit', [0])[0]
|
|
||||||
if time_limit == 0:
|
|
||||||
time_limit = -1
|
|
||||||
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))
|
|
||||||
|
|
||||||
# pass arguments to python-ldap
|
|
||||||
try:
|
|
||||||
id = self.conn.search_ext(
|
|
||||||
base_dn, scope, filter, attrs_list, timeout=time_limit,
|
|
||||||
sizelimit=size_limit
|
|
||||||
)
|
|
||||||
while True:
|
|
||||||
(objtype, res_list) = self.conn.result(id, 0)
|
|
||||||
if not res_list:
|
|
||||||
break
|
|
||||||
if objtype == _ldap.RES_SEARCH_ENTRY or \
|
|
||||||
(search_refs and objtype == _ldap.RES_SEARCH_REFERENCE):
|
|
||||||
res.append(res_list[0])
|
|
||||||
except (_ldap.ADMINLIMIT_EXCEEDED, _ldap.TIMELIMIT_EXCEEDED,
|
|
||||||
_ldap.SIZELIMIT_EXCEEDED), e:
|
|
||||||
truncated = True
|
|
||||||
except _ldap.LDAPError, e:
|
|
||||||
self.handle_errors(e)
|
|
||||||
|
|
||||||
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, normalize=normalize)
|
|
||||||
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, normalize=normalize)
|
|
||||||
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, base_dn=None):
|
|
||||||
"""
|
|
||||||
Find entry (dn, entry_attrs) by attribute and object class.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
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 '')
|
|
||||||
"""
|
|
||||||
|
|
||||||
if base_dn is None:
|
|
||||||
base_dn = DN()
|
|
||||||
assert isinstance(base_dn, DN)
|
|
||||||
|
|
||||||
search_kw = {attr: value, 'objectClass': object_class}
|
|
||||||
filter = self.make_filter(search_kw, rules=self.MATCH_ALL)
|
|
||||||
(entries, truncated) = self.find_entries(filter, attrs_list, base_dn)
|
|
||||||
|
|
||||||
if len(entries) > 1:
|
|
||||||
raise errors.SingleMatchExpected(found=len(entries))
|
|
||||||
else:
|
|
||||||
if truncated:
|
|
||||||
raise errors.LimitsExceeded()
|
|
||||||
else:
|
|
||||||
return entries[0]
|
|
||||||
|
|
||||||
def get_entry(self, dn, attrs_list=None, time_limit=None,
|
|
||||||
size_limit=None, normalize=True):
|
|
||||||
"""
|
|
||||||
Get entry (dn, entry_attrs) by dn.
|
|
||||||
|
|
||||||
Keyword arguments:
|
|
||||||
attrs_list - list of attributes to return, all if None (default None)
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert isinstance(dn, DN)
|
|
||||||
|
|
||||||
(entry, truncated) = self.find_entries(
|
|
||||||
None, attrs_list, dn, self.SCOPE_BASE, time_limit=time_limit,
|
|
||||||
size_limit=size_limit, normalize=normalize
|
|
||||||
)
|
|
||||||
|
|
||||||
if truncated:
|
|
||||||
raise errors.LimitsExceeded()
|
|
||||||
return entry[0]
|
|
||||||
|
|
||||||
config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]}
|
config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]}
|
||||||
def get_ipa_config(self, attrs_list=None):
|
def get_ipa_config(self, attrs_list=None):
|
||||||
"""Returns the IPA configuration entry (dn, entry_attrs)."""
|
"""Returns the IPA configuration entry (dn, entry_attrs)."""
|
||||||
@ -376,7 +233,7 @@ class ldap2(LDAPConnection, CrudBackend):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
config_entry = getattr(context, 'config_entry')
|
config_entry = getattr(context, 'config_entry')
|
||||||
return (cdn, copy.deepcopy(config_entry))
|
return copy.deepcopy(config_entry)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Not in our context yet
|
# Not in our context yet
|
||||||
pass
|
pass
|
||||||
@ -387,14 +244,14 @@ class ldap2(LDAPConnection, CrudBackend):
|
|||||||
)
|
)
|
||||||
if truncated:
|
if truncated:
|
||||||
raise errors.LimitsExceeded()
|
raise errors.LimitsExceeded()
|
||||||
(cdn, config_entry) = entry[0]
|
config_entry = entry[0]
|
||||||
except errors.NotFound:
|
except errors.NotFound:
|
||||||
config_entry = {}
|
config_entry = {}
|
||||||
for a in self.config_defaults:
|
for a in self.config_defaults:
|
||||||
if a not in config_entry:
|
if a not in config_entry:
|
||||||
config_entry[a] = self.config_defaults[a]
|
config_entry[a] = self.config_defaults[a]
|
||||||
setattr(context, 'config_entry', copy.deepcopy(config_entry))
|
context.config_entry = copy.deepcopy(config_entry)
|
||||||
return (cdn, config_entry)
|
return config_entry
|
||||||
|
|
||||||
def has_upg(self):
|
def has_upg(self):
|
||||||
"""Returns True/False whether User-Private Groups are enabled.
|
"""Returns True/False whether User-Private Groups are enabled.
|
||||||
@ -706,148 +563,6 @@ class ldap2(LDAPConnection, CrudBackend):
|
|||||||
# update group entry
|
# update group entry
|
||||||
self.update_entry(group_dn, group_entry_attrs)
|
self.update_entry(group_dn, group_entry_attrs)
|
||||||
|
|
||||||
def get_members(self, group_dn, members, attr_list=[], membertype=MEMBERS_ALL, time_limit=None, size_limit=None, normalize=True):
|
|
||||||
"""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:
|
|
||||||
user_container_dn = DN(api.env.container_user, api.env.basedn) # FIXME, initialize once
|
|
||||||
host_container_dn = DN(api.env.container_host, api.env.basedn)
|
|
||||||
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 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,
|
|
||||||
normalize=normalize)
|
|
||||||
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, ['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, normalize=True):
|
|
||||||
"""
|
|
||||||
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 = ["dn", "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, normalize=normalize)
|
|
||||||
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 set_entry_active(self, dn, active):
|
def set_entry_active(self, dn, active):
|
||||||
"""Mark entry active/inactive."""
|
"""Mark entry active/inactive."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user