Jan Zelený <jzel...@redhat.com> wrote: > Jan Zeleny <jzel...@redhat.com> wrote: > > Jan Zelený <jzel...@redhat.com> wrote: > > > Jan Zelený <jzel...@redhat.com> wrote: > > > > Rob Crittenden <rcrit...@redhat.com> wrote: > > > > > Jan Zelený wrote: > > > > > > Rob Crittenden<rcrit...@redhat.com> wrote: > > > > > >> Jan Zelený wrote: > > > > > >>> Loading of the schema is now performed in the first request > > > > > >>> that requires it. > > > > > >>> > > > > > >>> https://fedorahosted.org/freeipa/ticket/583 > > > > > >>> > > > > > >>> Jan > > > > > >> > > > > > >> We still need to enforce that we get the schema, some low-level > > > > > >> functions depend on it. Also, if the UI doesn't get its aciattrs > > > > > >> (which are derived from the schema) then nothing will be > > > > > >> editable. > > > > > >> > > > > > >> I'm getting this backtrace if I force no schema by disabling > > > > > > get_schema: > > > > > > Ok, I'm sending new version, it should handle these exceptions > > > > > > better and the operation should fail if it needs the schema and > > > > > > the schema is not available for some reason. > > > > > > > > > > This breaks the XML-RPC server. I fixed one problem: > > > > > --- a/ipaserver/plugins/ldap2.py > > > > > +++ b/ipaserver/plugins/ldap2.py > > > > > > > > > > @@ -253,9 +253,10 @@ class ldap2(CrudBackend, Encoder): > > > > > def get_syntax(self, attr, value): > > > > > if not self.schema: > > > > > - self.schema = get_schema(self.ldap_uri, self.conn) > > > > > - if not self.schema: > > > > > + schema = get_schema(self.ldap_uri, self.conn) > > > > > > > > > > + if not schema: > > > > > return None > > > > > > > > > > + object.__setattr__(self, 'schema', schema) > > > > > > > > > > obj = self.schema.get_obj(_ldap.schema.AttributeType, > > > > > attr) > > > > > > > > > > if obj is not None: > > > > > return obj.syntax > > > > > > > > > > But simply things like get_entry() return an InternalError now. I'm > > > > > not sure where you were going by adding this. > > > > > > > > > > rob > > > > > > > > Ok, no problem. It's possible that I simply did a mistake thinking I > > > > can do something in Python what is not really possible. > > > > > > > > About that InternalError: I think raising InternalError when we > > > > cannot load the schema to do the decoding is the right thing to do. > > > > Do you have a better solution? I thought about returning empty > > > > result, but that would mean we have to check the result in every > > > > funtction that is calling them and raising InternalError there. > > > > > > I'm sending updated patch. I modified the get_syntax() as you suggested > > > and I slightly modified raising that InternalError - currently it isn't > > > raised when results from get_entry() are not required by calling > > > method. Currently I'm running some tests, preliminary results looked > > > ok. > > > > self-nack > > > > I discovered some issues discovered by internal test suite, I'm working > > on them > > > > Jan > > Ok, everything is solved, I'm sending final version of the patch in the > attachment. But I still think this should go to 2.1, since it's quite > extensive patch in the core of IPA server and it has potential to break > many things. > > Jan
Rebased against master Jan
From 7ff1e505395c401a163ac479220910cb39e4182d Mon Sep 17 00:00:00 2001 From: Jan Zeleny <jzel...@redhat.com> Date: Tue, 15 Feb 2011 09:37:58 +0100 Subject: [PATCH] Don't load the LDAP schema during startup https://fedorahosted.org/freeipa/ticket/583 --- ipalib/encoder.py | 12 +++-- ipalib/plugins/baseldap.py | 21 ++++++++- ipalib/plugins/dns.py | 2 +- ipalib/plugins/host.py | 2 +- ipalib/plugins/permission.py | 4 +- ipalib/plugins/sudocmd.py | 2 +- ipaserver/install/dsinstance.py | 2 +- ipaserver/plugins/ldap2.py | 92 +++++++++++++++++++++++++++------------ 8 files changed, 96 insertions(+), 41 deletions(-) diff --git a/ipalib/encoder.py b/ipalib/encoder.py index f23e5659e848d37db1072ff59aa7e11796b0836c..762be9c096a4b7cbea03ecfcc82d03789951c0b5 100644 --- a/ipalib/encoder.py +++ b/ipalib/encoder.py @@ -56,9 +56,10 @@ class Encoder(object): self.encoder_settings = EncoderSettings() def _decode_dict_val(self, key, val): - f = self.encoder_settings.decode_dict_vals_table.get( - self.encoder_settings.decode_dict_vals_table_keygen(key, val) - ) + k = self.encoder_settings.decode_dict_vals_table_keygen(key, val) + if k is False: + return False + f = self.encoder_settings.decode_dict_vals_table.get(k) if f: return val return self.decode(val) @@ -154,7 +155,10 @@ class Encoder(object): tmp = self.encoder_settings.decode_postprocessor self.encoder_settings.decode_postprocessor = lambda x: x for (k, v) in dct.iteritems(): - dct[k] = self._decode_dict_val(k, v) + decoded_val = self._decode_dict_val(k, v) + if decoded_val is False: + return False + dct[k] = decoded_val if not self.encoder_settings.decode_dict_vals_postprocess: self.encoder_settings.decode_postprocessor = tmp return dct diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 4441e79602e2f8622e2638148db5fea22181e3f0..f1229169706ddde4b27d0773aa786ef976c29341 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -183,6 +183,9 @@ def get_effective_rights(ldap, dn, attrs=None): if attrs is None: attrs = ['*', 'nsaccountlock', 'cospriority'] rights = ldap.get_effective_rights(dn, attrs) + if rights[1] in False: + return False + rdict = {} if 'attributelevelrights' in rights[1]: rights = rights[1]['attributelevelrights'] @@ -388,7 +391,11 @@ class LDAPObject(Object): objectclasses += self.possible_objectclasses # Get list of available attributes for this object for use # in the ACI UI. - attrs = self.api.Backend.ldap2.schema.attribute_types(objectclasses) + schema = self.api.Backend.ldap2.get_schema() + if not schema: + attrs = [] + else: + attrs = schema.attribute_types(objectclasses) attrlist = [] # Go through the MUST first for (oid, attr) in attrs[0].iteritems(): @@ -607,6 +614,10 @@ class LDAPCreate(CallbackInterface, crud.Create): except errors.NotFound: self.obj.handle_not_found(*keys) + if entry_attrs is False: + # FIXME: is there any other action needed (some cleanup?) + raise errors.InternalError() + for callback in self.POST_CALLBACKS: if hasattr(callback, 'im_self'): dn = callback(ldap, dn, entry_attrs, *keys, **options) @@ -719,7 +730,9 @@ class LDAPRetrieve(LDAPQuery): self.obj.handle_not_found(*keys) if options.get('rights', False) and options.get('all', False): - entry_attrs['attributelevelrights'] = get_effective_rights(ldap, dn) + rights = get_effective_rights(ldap, dn) + if rights is not False: + entry_attrs['attributelevelrights'] = rights for callback in self.POST_CALLBACKS: if hasattr(callback, 'im_self'): @@ -878,7 +891,9 @@ class LDAPUpdate(LDAPQuery, crud.Update): ) if options.get('rights', False) and options.get('all', False): - entry_attrs['attributelevelrights'] = get_effective_rights(ldap, dn) + rights = get_effective_rights(ldap, dn) + if rights is not False: + entry_attrs['attributelevelrights'] = rights for callback in self.POST_CALLBACKS: if hasattr(callback, 'im_self'): diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py index a18940b3ac6268b587c691c440a81db492eead86..59b39b5e1bd440067b4c587c583f3abb284e93b0 100644 --- a/ipalib/plugins/dns.py +++ b/ipalib/plugins/dns.py @@ -217,7 +217,7 @@ def add_forward_record(zone, name, str_address): def dns_container_exists(ldap): try: - ldap.get_entry(api.env.container_dns, []) + ldap.get_entry(api.env.container_dns, [], override=True) except errors.NotFound: return False return True diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py index f5f5157b0ae8c5b6245b6cab3bcb21293b38d04d..321cc944816b640af3a05198ee446789f685b325 100644 --- a/ipalib/plugins/host.py +++ b/ipalib/plugins/host.py @@ -270,7 +270,7 @@ class host(LDAPObject): hostname = hostname[:-1] dn = super(host, self).get_dn(hostname, **options) try: - self.backend.get_entry(dn, ['']) + self.backend.get_entry(dn, [''], override=True) except errors.NotFound: try: (dn, entry_attrs) = self.backend.find_entry_by_attr( diff --git a/ipalib/plugins/permission.py b/ipalib/plugins/permission.py index db063334fddbe20ca3b5f7b2a57655da3a988896..164bf82d1a24c64684c4440e4eacbbd341d523cd 100644 --- a/ipalib/plugins/permission.py +++ b/ipalib/plugins/permission.py @@ -266,7 +266,7 @@ class permission_mod(LDAPUpdate): # check if permission is in LDAP try: (dn, attrs) = ldap.get_entry( - dn, attrs_list, normalize=self.obj.normalize_dn + dn, attrs_list, normalize=self.obj.normalize_dn, override=True ) except errors.NotFound: self.obj.handle_not_found(*keys) @@ -277,7 +277,7 @@ class permission_mod(LDAPUpdate): try: new_dn = dn.replace(keys[-1], options['rename'], 1) (new_dn, attrs) = ldap.get_entry( - new_dn, attrs_list, normalize=self.obj.normalize_dn + new_dn, attrs_list, normalize=self.obj.normalize_dn, override=True ) raise errors.DuplicateEntry() except errors.NotFound: diff --git a/ipalib/plugins/sudocmd.py b/ipalib/plugins/sudocmd.py index 528d79079bfb563254700e0b1f9b4148a4ccb6cb..d166652eeada933932848b243fa32ca359b95f77 100644 --- a/ipalib/plugins/sudocmd.py +++ b/ipalib/plugins/sudocmd.py @@ -81,7 +81,7 @@ class sudocmd(LDAPObject): keys[-1] = keys[-1][:-1] dn = super(sudocmd, self).get_dn(*keys, **options) try: - self.backend.get_entry(dn, ['']) + self.backend.get_entry(dn, [''], override=True) except errors.NotFound: try: (dn, entry_attrs) = self.backend.find_entry_by_attr( diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index bf631a67fb16b890e2e1933f59392e443de2cf0e..68ca45f9562cda442a6c5c4c49b91f2134d6f6c8 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -133,7 +133,7 @@ def has_managed_entries(host_name, dm_password): conn = ldap2(shared_instance=False, ldap_uri=ldapuri, base_dn='cn=config') conn.connect(bind_dn='cn=Directory Manager', bind_pw=dm_password) (dn, attrs) = conn.get_entry('cn=Managed Entries,cn=plugins', - ['*'], time_limit=2, size_limit=3000) + ['*'], time_limit=2, size_limit=3000, override=True) return True except errors.NotFound: return False diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index 568792d1b28f9e8152ad34251b2f303770601dae..ea310ac33fea24544dcd2ed8d568da0fbf7900fb 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -184,12 +184,6 @@ def get_schema(url, conn=None): return _ldap.schema.SubSchema(schema_entry[1]) -# cache schema when importing module -try: - _schema = get_schema(api.env.ldap_uri) -except AttributeError: - _schema = None - # The UPG setting will be cached the first time a module checks it _upg = None @@ -229,7 +223,6 @@ class ldap2(CrudBackend, Encoder): def __init__(self, shared_instance=True, ldap_uri=None, base_dn=None, schema=None): - global _schema CrudBackend.__init__(self, shared_instance=shared_instance) Encoder.__init__(self) self.encoder_settings.encode_dict_keys = True @@ -249,7 +242,7 @@ class ldap2(CrudBackend, Encoder): self.base_dn = api.env.basedn except AttributeError: self.base_dn = '' - self.schema = schema or _schema + self.schema = schema def __del__(self): if self.isconnected(): @@ -260,7 +253,11 @@ class ldap2(CrudBackend, Encoder): def get_syntax(self, attr, value): if not self.schema: - return None + schema = get_schema(self.ldap_uri, self.conn) + if not schema: + # we can't return None, since it could be mistaken for obj being None + return False + object.__setattr__(self, 'schema', schema) obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) if obj is not None: return obj.syntax @@ -269,7 +266,11 @@ class ldap2(CrudBackend, Encoder): def get_allowed_attributes(self, objectclasses): if not self.schema: - return [] + schema = get_schema(self.ldap_uri, self.conn) + if not schema: + # we can't return None, since it could be mistaken for obj being None + return [] + object.__setattr__(self, 'schema', schema) allowed_attributes = [] for oc in objectclasses: obj = self.schema.get_obj(_ldap.schema.ObjectClass, oc) @@ -286,10 +287,16 @@ class ldap2(CrudBackend, Encoder): If there is a problem loading the schema or the attribute is not in the schema return None """ - if self.schema: - obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) - return obj and obj.single_value - return None + if not self.schema: + schema = get_schema(self.ldap_uri, self.conn) + if not schema: + # if we cannot load the schema, it's safer to assume + # the attribute is single-valued + return True + object.__setattr__(self, 'schema', schema) + + obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) + return obj and obj.single_value @encode_args(2, 3, 'bind_dn', 'bind_pw') def create_connection(self, ccache=None, bind_dn='', bind_pw='', @@ -310,7 +317,6 @@ class ldap2(CrudBackend, Encoder): Extends backend.Connectible.create_connection. """ - global _schema if tls_cacertfile is not None: _ldap.set_option(_ldap.OPT_X_TLS_CACERTFILE, tls_cacertfile) if tls_certfile is not None: @@ -335,10 +341,10 @@ class ldap2(CrudBackend, Encoder): except _ldap.LDAPError, e: _handle_errors(e, **{}) - if self.schema is None and _schema is None: - # explicitly use setattr here so the schema can be set after - # the object is finalized. - object.__setattr__(self, 'schema', get_schema(self.ldap_uri, conn)) + # For now let's say the schema is None (will be loaded later) + # - explicitly use setattr here so the schema can be set after + # the object is finalized. + object.__setattr__(self, 'schema', None) return conn def destroy_connection(self): @@ -616,17 +622,20 @@ class ldap2(CrudBackend, Encoder): return self.find_entries(filter, attrs_list, base_dn)[0][0] def get_entry(self, dn, attrs_list=None, time_limit=None, - size_limit=None, normalize=True): + size_limit=None, normalize=True, override=False): """ Get entry (dn, entry_attrs) by dn. Keyword arguments: attrs_list - list of attributes to return, all if None (default None) """ - return self.find_entries( + entries = self.find_entries( None, attrs_list, dn, self.SCOPE_BASE, time_limit=time_limit, size_limit=size_limit, normalize=normalize )[0][0] + if entries[1] is False and override == False: + raise errors.InternalError() + return entries config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]} def get_ipa_config(self, attrs_list=None): @@ -639,14 +648,27 @@ class ldap2(CrudBackend, Encoder): )[0][0] except errors.NotFound: config_entry = {} + + if config_entry is False: + raise errors.InternalError() + for a in self.config_defaults: if a not in config_entry: config_entry[a] = self.config_defaults[a] return (cdn, config_entry) - def get_schema(self): - """Returns a copy of the current LDAP schema.""" - return copy.deepcopy(self.schema) + def get_schema(self, deepcopy=False): + """ Returns either a reference to current schema or its deep copy """ + if not self.schema: + schema = get_schema(self.ldap_uri, self.conn) + if not schema: + return None + object.__setattr__(self, 'schema', schema) + + if (deepcopy): + return copy.deepcopy(self.schema) + else: + return self.schema def has_upg(self): """Returns True/False whether User-Private Groups are enabled. @@ -691,6 +713,9 @@ class ldap2(CrudBackend, Encoder): on the attribute. This only operates on a single attribute at a time. """ (dn, attrs) = self.get_effective_rights(dn, [attr]) + if attrs is False: + return False + if 'attributelevelrights' in attrs: attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') (attr, rights) = attr_rights.split(':') @@ -705,6 +730,9 @@ class ldap2(CrudBackend, Encoder): on the attribute. This only operates on a single attribute at a time. """ (dn, attrs) = self.get_effective_rights(dn, [attr]) + if attrs is False: + return False + if 'attributelevelrights' in attrs: attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') (attr, rights) = attr_rights.split(':') @@ -728,6 +756,9 @@ class ldap2(CrudBackend, Encoder): on the entry. """ (dn, attrs) = self.get_effective_rights(dn, ["*"]) + if attrs is False: + return False + if 'entrylevelrights' in attrs: entry_rights = attrs['entrylevelrights'][0].decode('UTF-8') if 'd' in entry_rights: @@ -741,6 +772,9 @@ class ldap2(CrudBackend, Encoder): on the entry. """ (dn, attrs) = self.get_effective_rights(dn, ["*"]) + if attrs is False: + return False + if 'entrylevelrights' in attrs: entry_rights = attrs['entrylevelrights'][0].decode('UTF-8') if 'a' in entry_rights: @@ -861,7 +895,7 @@ class ldap2(CrudBackend, Encoder): is True. """ # check if the entry exists - (dn, entry_attrs) = self.get_entry(dn, ['objectclass']) + (dn, entry_attrs) = self.get_entry(dn, ['objectclass'], override=True) # get group entry (group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr]) @@ -931,6 +965,7 @@ class ldap2(CrudBackend, Encoder): size_limit=size_limit, normalize=normalize) except errors.NotFound: netresults = [] + results = results + netresults if membertype == MEMBERS_ALL: @@ -993,7 +1028,6 @@ class ldap2(CrudBackend, Encoder): size_limit=size_limit, normalize=normalize) except errors.NotFound: netresults = [] - results = results + netresults try: (pbacresults, truncated) = self.find_entries(searchfilter, attr_list, 'cn=pbac,%s' % api.env.basedn, @@ -1001,7 +1035,6 @@ class ldap2(CrudBackend, Encoder): normalize=normalize) except errors.NotFound: pbacresults = [] - results = results + pbacresults try: (sudoresults, truncated) = self.find_entries(searchfilter, attr_list, 'cn=sudo,%s' % api.env.basedn, @@ -1009,7 +1042,7 @@ class ldap2(CrudBackend, Encoder): normalize=normalize) except errors.NotFound: sudoresults = [] - results = results + sudoresults + results = results + netresults + pbacresults + sudoresults direct = [] indirect = [] @@ -1142,7 +1175,10 @@ class ldap2(CrudBackend, Encoder): (entries, truncated) = self.find_entries( filter, attrs_list, base_dn, scope ) + for (dn, entry_attrs) in entries: + if entry_attrs is False: + continue entry_attrs['dn'] = [dn] output.append(entry_attrs) -- 1.7.4
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel