diff --git a/management/univention-directory-manager-modules/modules/univention/admin/handlers/__init__.py b/management/univention-directory-manager-modules/modules/univention/admin/handlers/__init__.py index 042ad0a..ae0f028 100644 --- a/management/univention-directory-manager-modules/modules/univention/admin/handlers/__init__.py +++ b/management/univention-directory-manager-modules/modules/univention/admin/handlers/__init__.py @@ -72,9 +72,11 @@ def disable_ad_restrictions(disable=True): _prevent_to_change_ad_properties = disable -class base(object): +class simpleLdap(object): - def __init__(self, co, lo, position, dn='', superordinate = None ): + def __init__(self, co, lo, position, dn='', superordinate=None, attributes=None): + self._exists = False + self.exceptions = [] self.co = co self.lo = lo self.dn = dn @@ -107,8 +109,68 @@ def __init__(self, co, lo, position, dn='', superordinate = None ): self.old_options = [] self.alloc = [] + # s4connector_present is a global caching variable than can be + # None ==> ldap has not been checked for servers with service "S4 Connector" + # True ==> at least one server with IP address (aRecord) is present + # False ==> no server is present + global s4connector_present + if s4connector_present is None: + s4connector_present = False + searchResult = self.lo.search('(&(|(objectClass=univentionDomainController)(objectClass=univentionMemberServer))(univentionService=S4 Connector))', attr=['aRecord']) + s4connector_present = any(ddn for (ddn, attr) in searchResult if 'aRecord' in attr) + self.s4connector_present = s4connector_present + + if not univention.admin.modules.modules: + univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'univention.admin.modules.update() was not called') + univention.admin.modules.update() + + m = univention.admin.modules.get(self.module) + if not hasattr(self, 'mapping'): + self.mapping = getattr(m, 'mapping', None) + if not hasattr(self, 'descriptions'): + self.descriptions = getattr(m, 'property_descriptions', None) + + self.info = {} + self.oldattr = {} + if attributes: + self.oldattr = attributes + elif self.dn: + try: + self.oldattr = self.lo.get(self.dn, required=True) + except ldap.NO_SUCH_OBJECT: + pass + # raise univention.admin.uexceptions.noObject(self.dn) + + if self.oldattr: + self._exists = True + oldinfo = univention.admin.mapping.mapDict(self.mapping, self.oldattr) + oldinfo = self._post_unmap(oldinfo, self.oldattr) + self.info.update(oldinfo) + + self.policies = self.oldattr.get('univentionPolicyReference', []) + self.__set_options() + self.save() + + def exists( self ): + return self._exists + + def call_udm_property_hook(self, hookname, module, changes = None): + m = univention.admin.modules.get( module.module ) + if hasattr(m, 'extended_udm_attributes'): + for prop in m.extended_udm_attributes: + if prop.hook != None: + func = getattr(prop.hook, hookname, None) + if changes == None: + func(module) + else: + changes = func(module, changes) + return changes + def open(self): self._open = True + self.exceptions=[] + self.call_udm_property_hook('hook_open', self) + self.save() def save(self): '''saves current state as old state''' @@ -137,6 +199,16 @@ def diff(self): return changes + def _post_unmap( self, info, values ): + """This method can be overwritten to define special un-map + methods that can not be done with the default mapping API""" + return info + + def _post_map( self, modlist, diff ): + """This method can be overwritten to define special map methods + that can not be done with the default mapping API""" + return modlist + def hasChanged(self, key): '''checks if the given attribute(s) was (were) changed; key can either be a string (scalar) or a list''' @@ -294,6 +366,104 @@ def create(self): return self._create() + def _create(self): + self.exceptions = [] + self._ldap_pre_create() + self._update_policies() + self.call_udm_property_hook('hook_ldap_pre_create', self) + + # Make sure all default values are set ... + for name, p in self.descriptions.items(): + # ... if property has no option or any required option is currently enabled + if self.has_key(name) and self.descriptions[name].default(self): + self[name] # __getitem__ sets default value + + # iterate over all properties and call checkLdap() of corresponding syntax + self._call_checkLdap_on_all_property_syntaxes() + + al = self._ldap_addlist() + al.extend(self._ldap_modlist()) + m = univention.admin.modules.get(self.module) + + # evaluate extended attributes + ocs = set() + for prop in getattr(m, 'extended_udm_attributes', []): + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'simpleLdap._create: info[%s]:%r = %r'% (prop.name, self.has_key(prop.name), self.info.get(prop.name))) + if prop.syntax == 'boolean' and self.info.get(prop.name) == '0': + continue + if self.has_key(prop.name) and self.info.get(prop.name): + ocs.add(prop.objClass) + + # add object classes of (especially extended) options + for option in self.options: + try: + opt = m.options[option] + except KeyError: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, '%r does not specify option %r' % (m.module, option)) + continue + ocs |= set(opt.objectClasses) + + # remove duplicated object classes + for i in al: + key, val = i[0], i[-1] # might be a triple + if val and key.lower() == 'objectclass': + ocs -= set([val] if isinstance(val, basestring) else val) + if ocs: + al.append(('objectClass', list(ocs))) + + al = self.call_udm_property_hook('hook_ldap_addlist', self, al) + + # ensure univentionObject is set + al.append(('objectClass', ['univentionObject',])) + al.append(('univentionObjectType', [self.module,])) + + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, "create object with dn: %s" % (self.dn,)) + univention.debug.debug(univention.debug.ADMIN, 99, 'Create dn=%r;\naddlist=%r;' % (self.dn, al)) + self.lo.add(self.dn, al) + self._exists = True + + # if anything goes wrong we need to remove the already created object, otherwise we run into 'already exists' errors + try: + self._ldap_post_create() + except: + # ensure that there is no lock left + import traceback, sys + exc = sys.exc_info() + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, "Post-Create operation failed: %s" % (traceback.format_exc(),)) + try: + self.cancel() + except: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, "Post-create: cancel() failed: %s" % (traceback.format_exc(),)) + try: + self.remove() + except: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, "Post-create: remove() failed: %s" % (traceback.format_exc(),)) + raise exc[0], exc[1], exc[2] + + self.call_udm_property_hook('hook_ldap_post_create', self) + + self.save() + return self.dn + + def _ldap_pre_ready(self): + pass + + def _ldap_pre_create(self): + self.dn = self._ldap_dn() + + def _ldap_dn(self): + identifier = [] + for name, prop in self.descriptions.items(): + if prop.identifies: + identifier.append((self.mapping.mapName(name), self.mapping.mapValue(name, self.info[name]), 2)) + return '%s,%s' % (ldap.dn.dn2str([identifier]), self.position.getDn()) + + def _ldap_addlist(self): + return [] + + def _ldap_post_create(self): + pass + def modify(self, modify_childs=1,ignore_license=0): '''modify object''' @@ -306,559 +476,321 @@ def modify(self, modify_childs=1,ignore_license=0): return self._modify(modify_childs,ignore_license=ignore_license) - def _create_temporary_ou(self): - name = 'temporary_move_container_%s' % time.time() + def _modify(self, modify_childs=1, ignore_license=0): + self.exceptions = [] - module = univention.admin.modules.get('container/ou') - position = univention.admin.uldap.position('%s' % self.lo.base) + self.__prevent_ad_property_change() - temporary_object = module.object(None, self.lo, position) - temporary_object.open() - temporary_object['name'] = name - temporary_object.create() + self._ldap_pre_modify() + self._update_policies() + self.call_udm_property_hook('hook_ldap_pre_modify', self) - return 'ou=%s' % ldap.dn.escape_dn_chars(name) + # Make sure all default values are set... + for name, p in self.descriptions.items(): + # ... if property has no option or any required option is currently enabled + if self.has_key(name) and self.descriptions[name].default(self): + self[name] # __getitem__ sets default value - def _delete_temporary_ou_if_empty(self, temporary_ou): + # iterate over all properties and call checkLdap() of corresponding syntax + self._call_checkLdap_on_all_property_syntaxes() - if not temporary_ou: - return + ml = self._ldap_modlist() + ml = self.call_udm_property_hook('hook_ldap_modlist', self, ml) + ml = self._ldap_object_classes(ml) - dn = '%s,%s' %(temporary_ou,self.lo.base) + #FIXME: timeout without exception if objectClass of Object is not exsistant !! + univention.debug.debug(univention.debug.ADMIN, 99, 'Modify dn=%r;\nmodlist=%r;\noldattr=%r;' % (self.dn, ml, self.oldattr)) + self.lo.modify(self.dn, ml, ignore_license=ignore_license) - module = univention.admin.modules.get('container/ou') - temporary_object = univention.admin.modules.lookup(module, None, self.lo, scope='base', base=dn, required=1, unique=1)[0] - temporary_object.open() - try: - temporary_object.remove() - except (univention.admin.uexceptions.ldapError, ldap.NOT_ALLOWED_ON_NONLEAF): - pass + self._ldap_post_modify() + self.call_udm_property_hook('hook_ldap_post_modify', self) - def move(self, newdn, ignore_license=0, temporary_ou=None): - '''move object''' - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: called for %s to %s'% (self.dn,newdn)) + self.save() + return self.dn - if not (univention.admin.modules.supports(self.module,'move') - or univention.admin.modules.supports(self.module,'subtree_move')): # this should have been checked before, but I want to be sure... - raise univention.admin.uexceptions.invalidOperation + def _ldap_pre_modify(self): + pass - if not self.exists(): - raise univention.admin.uexceptions.noObject + def _ldap_post_modify(self): + pass - if _prevent_to_change_ad_properties and self._is_synced_object(): - raise univention.admin.uexceptions.invalidOperation(_('Objects from Active Directory can not be moved.')) + def _ldap_modlist(self): + self.exceptions = [] - goaldn = ','.join(ldap.explode_dn(newdn)[1:]) - goalmodule = univention.admin.modules.identifyOne(goaldn, self.lo.get(goaldn)) - goalmodule = univention.admin.modules.get(goalmodule) - if not goalmodule or not hasattr(goalmodule,'childs') or not goalmodule.childs == 1: - raise univention.admin.uexceptions.invalidOperation, _("Destination object can't have sub objects.") + diff_ml = self.diff() + ml = univention.admin.mapping.mapDiff(self.mapping, diff_ml) + ml = self._post_map(ml, diff_ml) - if self.dn.lower() == newdn.lower(): - if self.dn == newdn: - raise univention.admin.uexceptions.ldapError, _('Moving not possible: old and new DN are identical.') - else: - # We must use a temporary folder because OpenLDAP does not allow a rename of an container with subobjects - temporary_ou = self._create_temporary_ou() - new_rdn = ldap.explode_rdn(newdn)[0] - new_dn = '%s,%s,%s' %(new_rdn,temporary_ou,self.lo.base) - self.move(new_dn, ignore_license, temporary_ou) - self.dn = new_dn + # policies + if self.policies != self.oldpolicies: + if 'univentionPolicyReference' not in self.oldattr.get('objectClass', []): + ml.append(('objectClass', '', ['univentionPolicyReference'])) + ml.append(('univentionPolicyReference', self.oldpolicies, self.policies)) - if self.dn.lower() == newdn.lower()[-len(self.dn):]: - raise univention.admin.uexceptions.ldapError, _("Moving into one's own sub container not allowed.") - - if univention.admin.modules.supports(self.module,'subtree_move'): - # check if is subtree: - subelements = self.lo.search(base=self.dn, scope='one', attr=[]) - if subelements: - olddn = self.dn - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: found subelements, do subtree move: newdn: %s' % newdn) - # create copy of myself - module = univention.admin.modules.get(self.module) - position = univention.admin.uldap.position(self.lo.base) - position.setDn(','.join(ldap.explode_dn(newdn)[1:])) - copyobject = module.object(None, self.lo, position) - copyobject.open() - for key in self.keys(): - copyobject[key]=self[key] - copyobject.policies=self.policies - copyobject.create() - moved=[] - try: - for subolddn, suboldattrs in subelements: - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: subelement %s' % subolddn) - # Convert the DNs to lowercase before the replacement. The cases might be mixed up if the python lib is - # used by the connector, for example: - # subolddn: uid=user_test_h80,ou=TEST_H81,LDAP_BASE - # self.dn: ou=test_h81,LDAP_BASE - # newdn: OU=TEST_H81,ou=test_h82,$LDAP_BASE - rdn = ldap.explode_dn(subolddn)[0] - subolddn_dn_without_rdn_lower = ','.join(ldap.explode_dn(subolddn)[1:]).lower() - subnewdn = '%s,%s' % (rdn, subolddn_dn_without_rdn_lower.replace(self.dn.lower(),newdn)) - submodule = univention.admin.modules.identifyOne(subolddn, suboldattrs) - submodule = univention.admin.modules.get(submodule) - subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subolddn) - if not subobject or not (univention.admin.modules.supports(submodule,'move') or - univention.admin.modules.supports(submodule,'subtree_move')): - raise univention.admin.uexceptions.invalidOperation(_('Unable to move object %(name)s (%(type)s) in subtree, trying to revert changes.' ) % {'name': subolddn[:subolddn.find(',')], 'type': univention.admin.modules.identifyOne(subolddn, suboldattrs)}) - subobject.open() - subobject.move(subnewdn) - moved.append((subolddn,subnewdn)) - self.remove() - self._delete_temporary_ou_if_empty(temporary_ou) - except: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'move: subtree move failed, trying to move back.') - position=univention.admin.uldap.position(self.lo.base) - position.setDn(','.join(ldap.explode_dn(olddn)[1:])) - for subolddn, subnewdn in moved: - submodule = univention.admin.modules.identifyOne(subnewdn, self.lo.get(subnewdn)) - submodule = univention.admin.modules.get(submodule) - subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subnewdn) - subobject.open() - subobject.move(subolddn) - copyobject.remove() - self._delete_temporary_ou_if_empty(temporary_ou) - raise - else: - # normal move, fails on subtrees - res = self._move(newdn, ignore_license=ignore_license) - self._delete_temporary_ou_if_empty(temporary_ou) - return res - - else: - res = self._move(newdn, ignore_license=ignore_license) - self._delete_temporary_ou_if_empty(temporary_ou) - return res - - def move_subelements(self, olddn, newdn, subelements, ignore_license = False): - if subelements: - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: found subelements, do subtree move') - moved = [] - try: - for subolddn, suboldattrs in subelements: - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: subelement %s' % subolddn) - subnewdn = subolddn.replace(olddn, newdn) - submodule = univention.admin.modules.identifyOne(subolddn, suboldattrs) - submodule = univention.admin.modules.get(submodule) - subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subolddn) - if not subobject or not (univention.admin.modules.supports(submodule, 'move') or - univention.admin.modules.supports(submodule, 'subtree_move')): - raise univention.admin.uexceptions.invalidOperation(_('Unable to move object %(name)s (%(type)s) in subtree, trying to revert changes.') % {'name': subolddn[:subolddn.find(',')], 'type': univention.admin.modules.identifyOne(subolddn, suboldattrs)}) - subobject.open() - subobject._move(subnewdn) - moved.append((subolddn,subnewdn)) - return moved - except: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'move: subtree move failed, try to move back') - for subolddn, subnewdn in moved: - submodule = univention.admin.modules.identifyOne(subnewdn, self.lo.get(subnewdn)) - submodule = univention.admin.modules.get(submodule) - subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subnewdn) - subobject.open() - subobject.move(subolddn) - raise - - def remove(self, remove_childs=0): - '''remove object''' - - if not self.dn or not self.lo.get(self.dn): - raise univention.admin.uexceptions.noObject(self.dn) - - return self._remove(remove_childs) - - def get_gid_for_primary_group(self): - gidNum = '99999' - if self['primaryGroup']: - try: - gidNum = self.lo.getAttr(self['primaryGroup'], 'gidNumber', required=True)[0] - except ldap.NO_SUCH_OBJECT: - raise univention.admin.uexceptions.primaryGroup(self['primaryGroup']) - return gidNum - - def get_sid_for_primary_group(self): - try: - sidNum = self.lo.getAttr(self['primaryGroup'], 'sambaSID', required=True)[0] - except ldap.NO_SUCH_OBJECT: - raise univention.admin.uexceptions.primaryGroupWithoutSamba(self['primaryGroup']) - return sidNum - - def _update_policies(self): - pass - - def _ldap_pre_ready(self): - pass - - def _ldap_pre_create(self): - self.dn = self._ldap_dn() - - def _ldap_dn(self): - identifier = [] - for name, prop in self.descriptions.items(): - if prop.identifies: - identifier.append((self.mapping.mapName(name), self.mapping.mapValue(name, self.info[name]), 2)) - return '%s,%s' % (ldap.dn.dn2str([identifier]), self.position.getDn()) - - def _ldap_post_create(self): - pass - - def _ldap_pre_modify(self): - pass - - def _ldap_post_modify(self): - pass - - def _ldap_pre_move(self, newdn): - pass - - def _ldap_post_move(self, olddn): - pass - - def _ldap_pre_remove(self): - pass - - def _ldap_post_remove(self): - pass - -def _not_implemented_method(attr): - def _not_implemented_error(self, *args, **kwargs): - raise NotImplementedError('%s() not implemented by %s.%s().' % (attr, self.__module__, self.__class__.__name__)) - return _not_implemented_error - - -# add some default abstract methods -for _attr in ('_ldap_addlist', '_ldap_modlist', '_ldap_dellist', 'exists', '_move', 'cancel', '_remove', '_create', '_modify'): - if not hasattr(base, _attr): - setattr(base, _attr, _not_implemented_method(_attr)) - - -class simpleLdap(base): - - def __init__(self, co, lo, position, dn='', superordinate=None, attributes=None): - self._exists = False - self.exceptions = [] - base.__init__(self, co, lo, position, dn, superordinate) - - # s4connector_present is a global caching variable than can be - # None ==> ldap has not been checked for servers with service "S4 Connector" - # True ==> at least one server with IP address (aRecord) is present - # False ==> no server is present - global s4connector_present - if s4connector_present is None: - s4connector_present = False - searchResult = self.lo.search('(&(|(objectClass=univentionDomainController)(objectClass=univentionMemberServer))(univentionService=S4 Connector))', attr=['aRecord']) - s4connector_present = any(ddn for (ddn, attr) in searchResult if 'aRecord' in attr) - self.s4connector_present = s4connector_present - - if not univention.admin.modules.modules: - univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'univention.admin.modules.update() was not called') - univention.admin.modules.update() + return ml + def _ldap_object_classes(self, ml): m = univention.admin.modules.get(self.module) - if not hasattr(self, 'mapping'): - self.mapping = getattr(m, 'mapping', None) - if not hasattr(self, 'descriptions'): - self.descriptions = getattr(m, 'property_descriptions', None) - - self.info = {} - self.oldattr = {} - if attributes: - self.oldattr = attributes - elif self.dn: - try: - self.oldattr = self.lo.get(self.dn, required=True) - except ldap.NO_SUCH_OBJECT: - pass - # raise univention.admin.uexceptions.noObject(self.dn) - - if self.oldattr: - self._exists = True - oldinfo = univention.admin.mapping.mapDict(self.mapping, self.oldattr) - oldinfo = self._post_unmap(oldinfo, self.oldattr) - self.info.update(oldinfo) - - self.policies = self.oldattr.get('univentionPolicyReference', []) - self.__set_options() - self.save() - - def exists( self ): - return self._exists - - def call_udm_property_hook(self, hookname, module, changes = None): - m = univention.admin.modules.get( module.module ) - if hasattr(m, 'extended_udm_attributes'): - for prop in m.extended_udm_attributes: - if prop.hook != None: - func = getattr(prop.hook, hookname, None) - if changes == None: - func(module) - else: - changes = func(module, changes) - return changes - - def open(self): - base.open(self) - self.exceptions=[] - self.call_udm_property_hook('hook_open', self) - self.save() - - def _remove_option( self, name ): - if name in self.options: - self.options.remove( name ) - - def __set_options(self): - self.options = [] - options = univention.admin.modules.options(self.module) - if 'objectClass' in self.oldattr: - ocs = set(self.oldattr['objectClass']) - for opt, option in options.iteritems(): - if not option.disabled and option.matches(ocs): - self.options.append(opt) - else: - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'reset options to default by _define_options') - self._define_options(options) - - def _define_options( self, module_options ): - # enable all default options - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'modules/__init__.py _define_options: reset to default options') - for name, opt in module_options.items(): - if not opt.disabled and opt.default: - self.options.append( name ) - - def option_toggled(self, option): - '''Checks if an option was changed. This does not work for not yet existing objects.''' - return option in set(self.options) ^ set(self.old_options) - - def description(self): - if self.dn: - rdn = self.lo.explodeDn(self.dn)[0] - return rdn[rdn.find('=')+1:] - else: - return 'none' - - def _post_unmap( self, info, values ): - """This method can be overwritten to define special un-map - methods that can not be done with the default mapping API""" - return info - - def _post_map( self, modlist, diff ): - """This method can be overwritten to define special map methods - that can not be done with the default mapping API""" - return modlist - - def _ldap_modlist(self): - self.exceptions = [] - - diff_ml = self.diff() - ml = univention.admin.mapping.mapDiff(self.mapping, diff_ml) - ml = self._post_map(ml, diff_ml) - - # policies - if self.policies != self.oldpolicies: - if 'univentionPolicyReference' not in self.oldattr.get('objectClass', []): - ml.append(('objectClass', '', ['univentionPolicyReference'])) - ml.append(('univentionPolicyReference', self.oldpolicies, self.policies)) - - return ml - - def _create(self): - self.exceptions = [] - self._ldap_pre_create() - self._update_policies() - self.call_udm_property_hook('hook_ldap_pre_create', self) - - # Make sure all default values are set ... - for name, p in self.descriptions.items(): - # ... if property has no option or any required option is currently enabled - if self.has_key(name) and self.descriptions[name].default(self): - self[name] # __getitem__ sets default value + lowerset = lambda vals: set(x.lower() for x in vals) - # iterate over all properties and call checkLdap() of corresponding syntax - self._call_checkLdap_on_all_property_syntaxes() + ocs = lowerset(_MergedAttributes(self, ml).get_attribute('objectClass')) + unneeded_ocs = set() + required_ocs = set() - al = self._ldap_addlist() - al.extend(self._ldap_modlist()) - m = univention.admin.modules.get(self.module) + # evaluate (extended) options + module_options = univention.admin.modules.options(self.module) + available_options = set(module_options.keys()) + options = set(self.options) + old_options = set(self.old_options) + if options != old_options: + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'options=%r; old_options=%r' % (options, old_options)) + unavailable_options = (options - available_options) | (old_options - available_options) + if unavailable_options: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, '%r does not provide options: %r' % (self.module, unavailable_options)) + added_options = options - old_options - unavailable_options + removed_options = old_options - options - unavailable_options # evaluate extended attributes - ocs = set() for prop in getattr(m, 'extended_udm_attributes', []): - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'simpleLdap._create: info[%s]:%r = %r'% (prop.name, self.has_key(prop.name), self.info.get(prop.name))) - if prop.syntax == 'boolean' and self.info.get(prop.name) == '0': - continue - if self.has_key(prop.name) and self.info.get(prop.name): - ocs.add(prop.objClass) + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'simpleLdap._modify: extended attribute=%r oc=%r'% (prop.name, prop.objClass)) - # add object classes of (especially extended) options - for option in self.options: - try: - opt = m.options[option] - except KeyError: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, '%r does not specify option %r' % (m.module, option)) + if self.has_key(prop.name) and self.info.get(prop.name) and (True if prop.syntax != 'boolean' else self.info.get(prop.name) != '0'): + required_ocs |= set([prop.objClass]) continue - ocs |= set(opt.objectClasses) - # remove duplicated object classes - for i in al: - key, val = i[0], i[-1] # might be a triple - if val and key.lower() == 'objectclass': - ocs -= set([val] if isinstance(val, basestring) else val) - if ocs: - al.append(('objectClass', list(ocs))) + if prop.deleteObjClass: + unneeded_ocs |= set([prop.objClass]) - al = self.call_udm_property_hook('hook_ldap_addlist', self, al) + # if the value is unset we need to remove the attribute completely + if self.oldattr.get(prop.ldapMapping): + ml = [x for x in ml if x[0].lower() != prop.ldapMapping.lower()] + ml.append((prop.ldapMapping, self.oldattr.get(prop.ldapMapping), '')) - # ensure univentionObject is set - al.append(('objectClass', ['univentionObject',])) - al.append(('univentionObjectType', [self.module,])) + unneeded_ocs |= reduce(set.union, (set(module_options[option].objectClasses) for option in removed_options), set()) + required_ocs |= reduce(set.union, (set(module_options[option].objectClasses) for option in added_options), set()) - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, "create object with dn: %s" % (self.dn,)) - univention.debug.debug(univention.debug.ADMIN, 99, 'Create dn=%r;\naddlist=%r;' % (self.dn, al)) - self.lo.add(self.dn, al) - self._exists = True + ocs -= lowerset(unneeded_ocs) + ocs |= lowerset(required_ocs) + if lowerset(self.oldattr.get('objectClass', [])) == ocs: + return ml - # if anything goes wrong we need to remove the already created object, otherwise we run into 'already exists' errors - try: - self._ldap_post_create() - except: - # ensure that there is no lock left - import traceback, sys - exc = sys.exc_info() - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, "Post-Create operation failed: %s" % (traceback.format_exc(),)) - try: - self.cancel() - except: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, "Post-create: cancel() failed: %s" % (traceback.format_exc(),)) - try: - self.remove() - except: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, "Post-create: remove() failed: %s" % (traceback.format_exc(),)) - raise exc[0], exc[1], exc[2] + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'OCS=%r; required=%r; removed: %r' % (ocs, required_ocs, unneeded_ocs)) - self.call_udm_property_hook('hook_ldap_post_create', self) + # case normalize object class names + schema = self.lo.get_schema() + ocs = set(schema.get_obj(ldap.schema.models.ObjectClass, x).names[0] for x in ocs) - self.save() - return self.dn + # make sure we still have a structural object class + if not schema.get_structural_oc(ocs): + structural_ocs = schema.get_structural_oc(unneeded_ocs) + if not structural_ocs: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'missing structural object class. Modify will fail.') + return ml + univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'Preventing to remove last structural object class %r' % (structural_ocs,)) + ocs -= set(schema.get_obj(ldap.schema.models.ObjectClass, structural_ocs).names) - def _modify(self, modify_childs=1, ignore_license=0): - self.exceptions = [] + # validate removal of object classes + must, may = schema.attribute_types(ocs) + allowed = set(name.lower() for attr in may.values() for name in attr.names) | set(name.lower() for attr in must.values() for name in attr.names) - self.__prevent_ad_property_change() + ml = [x for x in ml if x[0].lower() != 'objectclass'] + ml.append(('objectClass', self.oldattr.get('objectClass', []), list(ocs))) + newattr = ldap.cidict.cidict(_MergedAttributes(self, ml).get_attributes()) - self._ldap_pre_modify() - self._update_policies() - self.call_udm_property_hook('hook_ldap_pre_modify', self) + # make sure only attributes known by the object classes are set + for attr, val in newattr.items(): + if not val: + continue + if re.sub(';binary$', '', attr.lower()) not in allowed: + univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'The attribute %r is not allowed by any object class.' % (attr,)) + # ml.append((attr, val, [])) # TODO: Remove the now invalid attribute instead + return ml - # Make sure all default values are set... - for name, p in self.descriptions.items(): - # ... if property has no option or any required option is currently enabled - if self.has_key(name) and self.descriptions[name].default(self): - self[name] # __getitem__ sets default value + # require all MUST attributes to be set + for attr in must.values(): + if not any(newattr.get(name) or newattr.get('%s;binary' % (name,)) for name in attr.names): + univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'The attribute %r is required by the current object classes.' % (attr.names,)) + return ml - # iterate over all properties and call checkLdap() of corresponding syntax - self._call_checkLdap_on_all_property_syntaxes() + ml = [x for x in ml if x[0].lower() != 'objectclass'] + ml.append(('objectClass', self.oldattr.get('objectClass', []), list(ocs))) - ml = self._ldap_modlist() - ml = self.call_udm_property_hook('hook_ldap_modlist', self, ml) - ml = self._ldap_object_classes(ml) + return ml - #FIXME: timeout without exception if objectClass of Object is not exsistant !! - univention.debug.debug(univention.debug.ADMIN, 99, 'Modify dn=%r;\nmodlist=%r;\noldattr=%r;' % (self.dn, ml, self.oldattr)) - self.lo.modify(self.dn, ml, ignore_license=ignore_license) + def move(self, newdn, ignore_license=0, temporary_ou=None): + '''move object''' + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: called for %s to %s'% (self.dn,newdn)) - self._ldap_post_modify() - self.call_udm_property_hook('hook_ldap_post_modify', self) + if not (univention.admin.modules.supports(self.module,'move') + or univention.admin.modules.supports(self.module,'subtree_move')): # this should have been checked before, but I want to be sure... + raise univention.admin.uexceptions.invalidOperation - self.save() - return self.dn + if not self.exists(): + raise univention.admin.uexceptions.noObject - def _ldap_object_classes(self, ml): - m = univention.admin.modules.get(self.module) - lowerset = lambda vals: set(x.lower() for x in vals) + if _prevent_to_change_ad_properties and self._is_synced_object(): + raise univention.admin.uexceptions.invalidOperation(_('Objects from Active Directory can not be moved.')) - ocs = lowerset(_MergedAttributes(self, ml).get_attribute('objectClass')) - unneeded_ocs = set() - required_ocs = set() + goaldn = ','.join(ldap.explode_dn(newdn)[1:]) + goalmodule = univention.admin.modules.identifyOne(goaldn, self.lo.get(goaldn)) + goalmodule = univention.admin.modules.get(goalmodule) + if not goalmodule or not hasattr(goalmodule,'childs') or not goalmodule.childs == 1: + raise univention.admin.uexceptions.invalidOperation, _("Destination object can't have sub objects.") - # evaluate (extended) options - module_options = univention.admin.modules.options(self.module) - available_options = set(module_options.keys()) - options = set(self.options) - old_options = set(self.old_options) - if options != old_options: - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'options=%r; old_options=%r' % (options, old_options)) - unavailable_options = (options - available_options) | (old_options - available_options) - if unavailable_options: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, '%r does not provide options: %r' % (self.module, unavailable_options)) - added_options = options - old_options - unavailable_options - removed_options = old_options - options - unavailable_options + if self.dn.lower() == newdn.lower(): + if self.dn == newdn: + raise univention.admin.uexceptions.ldapError, _('Moving not possible: old and new DN are identical.') + else: + # We must use a temporary folder because OpenLDAP does not allow a rename of an container with subobjects + temporary_ou = self._create_temporary_ou() + new_rdn = ldap.explode_rdn(newdn)[0] + new_dn = '%s,%s,%s' %(new_rdn,temporary_ou,self.lo.base) + self.move(new_dn, ignore_license, temporary_ou) + self.dn = new_dn - # evaluate extended attributes - for prop in getattr(m, 'extended_udm_attributes', []): - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'simpleLdap._modify: extended attribute=%r oc=%r'% (prop.name, prop.objClass)) + if self.dn.lower() == newdn.lower()[-len(self.dn):]: + raise univention.admin.uexceptions.ldapError, _("Moving into one's own sub container not allowed.") + + if univention.admin.modules.supports(self.module,'subtree_move'): + # check if is subtree: + subelements = self.lo.search(base=self.dn, scope='one', attr=[]) + if subelements: + olddn = self.dn + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: found subelements, do subtree move: newdn: %s' % newdn) + # create copy of myself + module = univention.admin.modules.get(self.module) + position = univention.admin.uldap.position(self.lo.base) + position.setDn(','.join(ldap.explode_dn(newdn)[1:])) + copyobject = module.object(None, self.lo, position) + copyobject.open() + for key in self.keys(): + copyobject[key]=self[key] + copyobject.policies=self.policies + copyobject.create() + moved=[] + try: + for subolddn, suboldattrs in subelements: + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: subelement %s' % subolddn) + # Convert the DNs to lowercase before the replacement. The cases might be mixed up if the python lib is + # used by the connector, for example: + # subolddn: uid=user_test_h80,ou=TEST_H81,LDAP_BASE + # self.dn: ou=test_h81,LDAP_BASE + # newdn: OU=TEST_H81,ou=test_h82,$LDAP_BASE + rdn = ldap.explode_dn(subolddn)[0] + subolddn_dn_without_rdn_lower = ','.join(ldap.explode_dn(subolddn)[1:]).lower() + subnewdn = '%s,%s' % (rdn, subolddn_dn_without_rdn_lower.replace(self.dn.lower(),newdn)) + submodule = univention.admin.modules.identifyOne(subolddn, suboldattrs) + submodule = univention.admin.modules.get(submodule) + subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subolddn) + if not subobject or not (univention.admin.modules.supports(submodule,'move') or + univention.admin.modules.supports(submodule,'subtree_move')): + raise univention.admin.uexceptions.invalidOperation(_('Unable to move object %(name)s (%(type)s) in subtree, trying to revert changes.' ) % {'name': subolddn[:subolddn.find(',')], 'type': univention.admin.modules.identifyOne(subolddn, suboldattrs)}) + subobject.open() + subobject.move(subnewdn) + moved.append((subolddn,subnewdn)) + self.remove() + self._delete_temporary_ou_if_empty(temporary_ou) + except: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'move: subtree move failed, trying to move back.') + position=univention.admin.uldap.position(self.lo.base) + position.setDn(','.join(ldap.explode_dn(olddn)[1:])) + for subolddn, subnewdn in moved: + submodule = univention.admin.modules.identifyOne(subnewdn, self.lo.get(subnewdn)) + submodule = univention.admin.modules.get(submodule) + subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subnewdn) + subobject.open() + subobject.move(subolddn) + copyobject.remove() + self._delete_temporary_ou_if_empty(temporary_ou) + raise + else: + # normal move, fails on subtrees + res = self._move(newdn, ignore_license=ignore_license) + self._delete_temporary_ou_if_empty(temporary_ou) + return res - if self.has_key(prop.name) and self.info.get(prop.name) and (True if prop.syntax != 'boolean' else self.info.get(prop.name) != '0'): - required_ocs |= set([prop.objClass]) - continue + else: + res = self._move(newdn, ignore_license=ignore_license) + self._delete_temporary_ou_if_empty(temporary_ou) + return res - if prop.deleteObjClass: - unneeded_ocs |= set([prop.objClass]) + def _ldap_pre_move(self, newdn): + pass - # if the value is unset we need to remove the attribute completely - if self.oldattr.get(prop.ldapMapping): - ml = [x for x in ml if x[0].lower() != prop.ldapMapping.lower()] - ml.append((prop.ldapMapping, self.oldattr.get(prop.ldapMapping), '')) + def _create_temporary_ou(self): + name = 'temporary_move_container_%s' % time.time() - unneeded_ocs |= reduce(set.union, (set(module_options[option].objectClasses) for option in removed_options), set()) - required_ocs |= reduce(set.union, (set(module_options[option].objectClasses) for option in added_options), set()) + module = univention.admin.modules.get('container/ou') + position = univention.admin.uldap.position('%s' % self.lo.base) - ocs -= lowerset(unneeded_ocs) - ocs |= lowerset(required_ocs) - if lowerset(self.oldattr.get('objectClass', [])) == ocs: - return ml + temporary_object = module.object(None, self.lo, position) + temporary_object.open() + temporary_object['name'] = name + temporary_object.create() - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'OCS=%r; required=%r; removed: %r' % (ocs, required_ocs, unneeded_ocs)) + return 'ou=%s' % ldap.dn.escape_dn_chars(name) - # case normalize object class names - schema = self.lo.get_schema() - ocs = set(schema.get_obj(ldap.schema.models.ObjectClass, x).names[0] for x in ocs) + def _delete_temporary_ou_if_empty(self, temporary_ou): - # make sure we still have a structural object class - if not schema.get_structural_oc(ocs): - structural_ocs = schema.get_structural_oc(unneeded_ocs) - if not structural_ocs: - univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'missing structural object class. Modify will fail.') - return ml - univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'Preventing to remove last structural object class %r' % (structural_ocs,)) - ocs -= set(schema.get_obj(ldap.schema.models.ObjectClass, structural_ocs).names) + if not temporary_ou: + return - # validate removal of object classes - must, may = schema.attribute_types(ocs) - allowed = set(name.lower() for attr in may.values() for name in attr.names) | set(name.lower() for attr in must.values() for name in attr.names) + dn = '%s,%s' %(temporary_ou,self.lo.base) - ml = [x for x in ml if x[0].lower() != 'objectclass'] - ml.append(('objectClass', self.oldattr.get('objectClass', []), list(ocs))) - newattr = ldap.cidict.cidict(_MergedAttributes(self, ml).get_attributes()) + module = univention.admin.modules.get('container/ou') + temporary_object = univention.admin.modules.lookup(module, None, self.lo, scope='base', base=dn, required=1, unique=1)[0] + temporary_object.open() + try: + temporary_object.remove() + except (univention.admin.uexceptions.ldapError, ldap.NOT_ALLOWED_ON_NONLEAF): + pass - # make sure only attributes known by the object classes are set - for attr, val in newattr.items(): - if not val: - continue - if re.sub(';binary$', '', attr.lower()) not in allowed: - univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'The attribute %r is not allowed by any object class.' % (attr,)) - # ml.append((attr, val, [])) # TODO: Remove the now invalid attribute instead - return ml + def move_subelements(self, olddn, newdn, subelements, ignore_license = False): + if subelements: + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: found subelements, do subtree move') + moved = [] + try: + for subolddn, suboldattrs in subelements: + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'move: subelement %s' % subolddn) + subnewdn = subolddn.replace(olddn, newdn) + submodule = univention.admin.modules.identifyOne(subolddn, suboldattrs) + submodule = univention.admin.modules.get(submodule) + subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subolddn) + if not subobject or not (univention.admin.modules.supports(submodule, 'move') or + univention.admin.modules.supports(submodule, 'subtree_move')): + raise univention.admin.uexceptions.invalidOperation(_('Unable to move object %(name)s (%(type)s) in subtree, trying to revert changes.') % {'name': subolddn[:subolddn.find(',')], 'type': univention.admin.modules.identifyOne(subolddn, suboldattrs)}) + subobject.open() + subobject._move(subnewdn) + moved.append((subolddn,subnewdn)) + return moved + except: + univention.debug.debug(univention.debug.ADMIN, univention.debug.ERROR, 'move: subtree move failed, try to move back') + for subolddn, subnewdn in moved: + submodule = univention.admin.modules.identifyOne(subnewdn, self.lo.get(subnewdn)) + submodule = univention.admin.modules.get(submodule) + subobject = univention.admin.objects.get(submodule, None, self.lo, position='', dn=subnewdn) + subobject.open() + subobject.move(subolddn) + raise - # require all MUST attributes to be set - for attr in must.values(): - if not any(newattr.get(name) or newattr.get('%s;binary' % (name,)) for name in attr.names): - univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'The attribute %r is required by the current object classes.' % (attr.names,)) - return ml + def _move(self, newdn, modify_childs=1, ignore_license=0): + self._ldap_pre_move(newdn) - ml = [x for x in ml if x[0].lower() != 'objectclass'] - ml.append(('objectClass', self.oldattr.get('objectClass', []), list(ocs))) + olddn = self.dn + self.lo.rename(self.dn, newdn) + self.dn = newdn - return ml + try: + self._move_in_groups(olddn) # can be done always, will do nothing if oldinfo has no attribute 'groups' + self._move_in_subordinates(olddn) + self._ldap_post_move(olddn) + except: + # move back + univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, + 'simpleLdap._move: self._ldap_post_move failed, move object back to %s'%olddn) + self.lo.rename(self.dn, olddn) + self.dn = olddn + raise def _move_in_subordinates(self, olddn): result = self.lo.search(base=self.lo.base, filter=filter_format('(&(objectclass=person)(secretary=%s))', [olddn]), attr=['dn']) @@ -876,24 +808,17 @@ def _move_in_groups(self, olddn): newmembers.append(self.dn) self.lo.modify(group, [('uniqueMember', members, newmembers)]) - def _move(self, newdn, modify_childs=1, ignore_license=0): - self._ldap_pre_move(newdn) - olddn = self.dn - self.lo.rename(self.dn, newdn) - self.dn = newdn + def _ldap_post_move(self, olddn): + pass - try: - self._move_in_groups(olddn) # can be done always, will do nothing if oldinfo has no attribute 'groups' - self._move_in_subordinates(olddn) - self._ldap_post_move(olddn) - except: - # move back - univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, - 'simpleLdap._move: self._ldap_post_move failed, move object back to %s'%olddn) - self.lo.rename(self.dn, olddn) - self.dn = olddn - raise + def remove(self, remove_childs=0): + '''remove object''' + + if not self.dn or not self.lo.get(self.dn): + raise univention.admin.uexceptions.noObject(self.dn) + + return self._remove(remove_childs) def _remove(self, remove_childs=0): univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO,'handlers/__init__._remove() called for %s' % self.dn) @@ -927,6 +852,62 @@ def _remove(self, remove_childs=0): self.call_udm_property_hook('hook_ldap_post_remove', self) self.save() + def _ldap_pre_remove(self): + pass + + def _ldap_post_remove(self): + pass + + def get_gid_for_primary_group(self): + gidNum = '99999' + if self['primaryGroup']: + try: + gidNum = self.lo.getAttr(self['primaryGroup'], 'gidNumber', required=True)[0] + except ldap.NO_SUCH_OBJECT: + raise univention.admin.uexceptions.primaryGroup(self['primaryGroup']) + return gidNum + + def get_sid_for_primary_group(self): + try: + sidNum = self.lo.getAttr(self['primaryGroup'], 'sambaSID', required=True)[0] + except ldap.NO_SUCH_OBJECT: + raise univention.admin.uexceptions.primaryGroupWithoutSamba(self['primaryGroup']) + return sidNum + + def _remove_option( self, name ): + if name in self.options: + self.options.remove( name ) + + def __set_options(self): + self.options = [] + options = univention.admin.modules.options(self.module) + if 'objectClass' in self.oldattr: + ocs = set(self.oldattr['objectClass']) + for opt, option in options.iteritems(): + if not option.disabled and option.matches(ocs): + self.options.append(opt) + else: + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'reset options to default by _define_options') + self._define_options(options) + + def _define_options( self, module_options ): + # enable all default options + univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO, 'modules/__init__.py _define_options: reset to default options') + for name, opt in module_options.items(): + if not opt.disabled and opt.default: + self.options.append( name ) + + def option_toggled(self, option): + '''Checks if an option was changed. This does not work for not yet existing objects.''' + return option in set(self.options) ^ set(self.old_options) + + def description(self): + if self.dn: + rdn = self.lo.explodeDn(self.dn)[0] + return rdn[rdn.find('=')+1:] + else: + return 'none' + def loadPolicyObject(self, policy_type, reset=0): pathlist=[] errors=0 @@ -2583,22 +2564,6 @@ def __setitem__(self, key, value): if raise_after: raise raise_after -class simpleLdapSub(simpleLdap): - - def __init__(self, co, lo, position, dn='', superordinate=None, attributes = [] ): - base.__init__(self, co, lo, position, dn, superordinate ) - - def _create(self): - self._modify() - - def _remove(self, remove_childs=0): - univention.debug.debug(univention.debug.ADMIN, univention.debug.INFO,'_remove() called') - self._ldap_pre_remove() - - ml=self._ldap_dellist() - self.lo.modify(self.dn, ml) - - self._ldap_post_remove() class simplePolicy(simpleLdap): def __init__(self, co, lo, position, dn='', superordinate=None, attributes = [] ):