View | Details | Raw Unified | Return to bug 33621 | Differences between
and this patch

Collapse All | Expand All

(-)modules/univention/s4connector/__init__.py (-112 / +71 lines)
 Lines 44-50    Link Here 
44
from signal import *
44
from signal import *
45
term_signal_caught = False
45
term_signal_caught = False
46
46
47
from univention.s4connector.s4cache import S4Cache
48
import sqlite3 as lite
47
import sqlite3 as lite
49
48
50
univention.admin.modules.update()
49
univention.admin.modules.update()
 Lines 314-320    Link Here 
314
		return self.config.has_key(section) and self.config[section].has_key(option)
313
		return self.config.has_key(section) and self.config[section].has_key(option)
315
314
316
class attribute:
315
class attribute:
317
	def __init__ ( self, ucs_attribute='', ldap_attribute='', con_attribute='', con_other_attribute='', required=0, single_value=False, compare_function=None, mapping=(), reverse_attribute_check=False, sync_mode='sync' ):
316
	def __init__ ( self, ucs_attribute='', ldap_attribute='', con_attribute='', con_other_attribute='', required=0, compare_function=None, mapping=(), reverse_attribute_check=False, sync_mode='sync' ):
318
		self.ucs_attribute=ucs_attribute
317
		self.ucs_attribute=ucs_attribute
319
		self.ldap_attribute=ldap_attribute
318
		self.ldap_attribute=ldap_attribute
320
		self.con_attribute=con_attribute
319
		self.con_attribute=con_attribute
 Lines 331-337    Link Here 
331
		# Seee https://forge.univention.org/bugzilla/show_bug.cgi?id=25823
330
		# Seee https://forge.univention.org/bugzilla/show_bug.cgi?id=25823
332
		self.reverse_attribute_check=reverse_attribute_check
331
		self.reverse_attribute_check=reverse_attribute_check
333
		self.sync_mode = sync_mode
332
		self.sync_mode = sync_mode
334
		self.single_value=single_value
335
333
336
class property:
334
class property:
337
	def __init__(	self, ucs_default_dn='', con_default_dn='', ucs_module='', ucs_module_others=[], sync_mode='', scope='', con_search_filter='', ignore_filter=None, match_filter=None, ignore_subtree=[],
335
	def __init__(	self, ucs_default_dn='', con_default_dn='', ucs_module='', ucs_module_others=[], sync_mode='', scope='', con_search_filter='', ignore_filter=None, match_filter=None, ignore_subtree=[],
 Lines 408-416    Link Here 
408
		configdbfile='/etc/univention/%s/s4internal.sqlite' % self.CONFIGBASENAME
406
		configdbfile='/etc/univention/%s/s4internal.sqlite' % self.CONFIGBASENAME
409
		self.config = configdb(configdbfile)
407
		self.config = configdb(configdbfile)
410
408
411
		s4cachedbfile='/etc/univention/%s/s4cache.sqlite' % self.CONFIGBASENAME
412
		self.s4cache = S4Cache(s4cachedbfile)
413
414
		configfile='/etc/univention/%s/s4internal.cfg' % self.CONFIGBASENAME
409
		configfile='/etc/univention/%s/s4internal.cfg' % self.CONFIGBASENAME
415
		if os.path.exists(configfile):
410
		if os.path.exists(configfile):
416
			ud.debug(ud.LDAP, ud.PROCESS, "Converting %s into a sqlite database" % configfile)
411
			ud.debug(ud.LDAP, ud.PROCESS, "Converting %s into a sqlite database" % configfile)
 Lines 429-435    Link Here 
429
		
424
		
430
		self.open_ucs()
425
		self.open_ucs()
431
426
432
		for section in ['DN Mapping UCS','DN Mapping CON','UCS rejected', 'UCS deleted']:
427
		for section in ['DN Mapping UCS','DN Mapping CON','UCS rejected']:
433
			if not self.config.has_section(section):
428
			if not self.config.has_section(section):
434
				self.config.add_section(section)				
429
				self.config.add_section(section)				
435
430
 Lines 706-716    Link Here 
706
				if key:
701
				if key:
707
					break
702
					break
708
				
703
				
709
			entryUUID = new.get('entryUUID')[0]
710
			if entryUUID:
711
				if self.was_entryUUID_deleted(entryUUID):
712
					ud.debug(ud.LDAP, ud.PROCESS, "__sync_file_from_ucs: Object with entryUUID %s was already deleted. Don't recreate." % entryUUID)
713
					return True
714
			#ud.debug(ud.LDAP, ud.INFO, "__sync_file_from_ucs: old: %s" % old)
704
			#ud.debug(ud.LDAP, ud.INFO, "__sync_file_from_ucs: old: %s" % old)
715
			#ud.debug(ud.LDAP, ud.INFO, "__sync_file_from_ucs: new: %s" % new)
705
			#ud.debug(ud.LDAP, ud.INFO, "__sync_file_from_ucs: new: %s" % new)
716
			if old and new:
706
			if old and new:
 Lines 774-781    Link Here 
774
				if not self._ignore_object(key,object) or ignore_subtree_match:
764
				if not self._ignore_object(key,object) or ignore_subtree_match:
775
					ud.debug(ud.LDAP, ud.INFO, "__sync_file_from_ucs: finished mapping")
765
					ud.debug(ud.LDAP, ud.INFO, "__sync_file_from_ucs: finished mapping")
776
					try:				
766
					try:				
777
						if ((old_dn and not self.sync_from_ucs(key, object, premapped_ucs_dn, unicode(old_dn,'utf8'), old, new))
767
						if ((old_dn and not self.sync_from_ucs(key, object, premapped_ucs_dn, unicode(old_dn,'utf8'), old))
778
							or (not old_dn and not self.sync_from_ucs(key, object, premapped_ucs_dn, old_dn, old, new))):
768
							or (not old_dn and not self.sync_from_ucs(key, object, premapped_ucs_dn, old_dn, old))):
779
							self._save_rejected_ucs(filename, dn)
769
							self._save_rejected_ucs(filename, dn)
780
							return False
770
							return False
781
						else:
771
						else:
 Lines 921-926    Link Here 
921
		# dummy
911
		# dummy
922
		pass
912
		pass
923
913
914
	def _generate_dn_list_from(self, files):
915
		'''
916
		Save all filenames in a dictonary with dn as key
917
		If more than one pickle file was created for one DN we could skip the first one
918
		'''
919
		if len(files) > 200:
920
			# Show an info if it takes some time
921
			ud.debug(ud.LDAP, ud.PROCESS, 'Scan all changes from UCS ...')
922
		self.dn_list = {}
923
		for listener_file in files:
924
			filename = os.path.join(self.listener_dir, listener_file)
925
			if not filename == "%s/tmp" % self.baseConfig['%s/s4/listener/dir' % self.CONFIGBASENAME]:
926
				if not filename in self.rejected_files:
927
					try:
928
						f=file(filename,'r')
929
					except IOError: # file not found so there's nothing to sync
930
						continue
931
932
					dn,new,old,old_dn=cPickle.load(f)
933
					if not self.dn_list.get(dn):
934
						self.dn_list[dn]=[filename]
935
					else:
936
						self.dn_list[dn].append(filename)
937
924
	def poll_ucs(self):
938
	def poll_ucs(self):
925
		'''
939
		'''
926
		poll changes from UCS: iterates over files exported by directory-listener module
940
		poll changes from UCS: iterates over files exported by directory-listener module
 Lines 945-950    Link Here 
945
		# the change list is too long and it took too much time
959
		# the change list is too long and it took too much time
946
		files = files[:MAX_SYNC_IN_ONE_INTERVAL]
960
		files = files[:MAX_SYNC_IN_ONE_INTERVAL]
947
961
962
		# Create a dictonary with all DNs
963
		self._generate_dn_list_from(files)
964
948
		# We may dropped the parent object, so don't show the traceback in any case
965
		# We may dropped the parent object, so don't show the traceback in any case
949
		traceback_level = ud.WARN
966
		traceback_level = ud.WARN
950
967
 Lines 956-983    Link Here 
956
					try:
973
					try:
957
						f=file(filename,'r')
974
						f=file(filename,'r')
958
					except IOError: # file not found so there's nothing to sync
975
					except IOError: # file not found so there's nothing to sync
976
						if self.dn_list.get(dn):
977
							self.dn_list[dn].remove(filename)
959
						continue
978
						continue
960
979
961
					dn,new,old,old_dn=cPickle.load(f)
980
					dn,new,old,old_dn=cPickle.load(f)
962
981
963
					for i in [0, 1]: # do it twice if the LDAP connection was closed
982
					if len(self.dn_list.get(dn, [])) < 2 or not old or not new:
983
						# If the list contains more then one file, the DN will be synced later
984
						# But if the object was added or remoed, the synchonization is required
985
						for i in [0, 1]: # do it twice if the LDAP connection was closed
986
							try:
987
								sync_successfull = self.__sync_file_from_ucs(filename, traceback_level=traceback_level)
988
							except (ldap.SERVER_DOWN, SystemExit):
989
								# once again, ldap idletimeout ...
990
								if i == 0:
991
									self.open_ucs()
992
									continue
993
								raise
994
							except:
995
								self._save_rejected_ucs(filename, dn)
996
								# We may dropped the parent object, so don't show this warning
997
								self._debug_traceback(traceback_level, "sync failed, saved as rejected \n\t%s" % filename)					
998
							if sync_successfull:
999
								os.remove(os.path.join(self.listener_dir,listener_file))
1000
								change_counter += 1
1001
							break
1002
					else:
1003
						os.remove(os.path.join(filename))
1004
						traceback_level = ud.INFO
964
						try:
1005
						try:
965
							sync_successfull = self.__sync_file_from_ucs(filename, traceback_level=traceback_level)
1006
							ud.debug(ud.LDAP, ud.PROCESS, 'Drop %s. The DN %s will synced later' % (filename, dn))
966
						except (ldap.SERVER_DOWN, SystemExit):
967
							# once again, ldap idletimeout ...
968
							if i == 0:
969
								self.open_ucs()
970
								continue
971
							raise
972
						except:
1007
						except:
973
							self._save_rejected_ucs(filename, dn)
1008
							ud.debug(ud.LDAP, ud.PROCESS, 'Drop %s. The object will synced later' % (filename))
974
							# We may dropped the parent object, so don't show this warning
975
							self._debug_traceback(traceback_level, "sync failed, saved as rejected \n\t%s" % filename)					
976
						if sync_successfull:
977
							os.remove(os.path.join(self.listener_dir,listener_file))
978
							change_counter += 1
979
						break
980
1009
1010
					if self.dn_list.get(dn):
1011
						self.dn_list[dn].remove(filename)
1012
981
				done_counter += 1
1013
				done_counter += 1
982
				print "%s"%done_counter,
1014
				print "%s"%done_counter,
983
				sys.stdout.flush()
1015
				sys.stdout.flush()
 Lines 1002-1008    Link Here 
1002
		_d=ud.function('ldap.__set_value')
1034
		_d=ud.function('ldap.__set_value')
1003
		if not modtype == 'add':
1035
		if not modtype == 'add':
1004
			ucs_object.open()
1036
			ucs_object.open()
1005
		ud.debug(ud.LDAP, ud.INFO, '__set_values: object: %s' % object)
1006
		def set_values(attributes):
1037
		def set_values(attributes):
1007
			if object['attributes'].has_key(attributes.ldap_attribute):
1038
			if object['attributes'].has_key(attributes.ldap_attribute):
1008
				ucs_key = attributes.ucs_attribute
1039
				ucs_key = attributes.ucs_attribute
 Lines 1058-1067    Link Here 
1058
						else:
1089
						else:
1059
							equal = compare[0] == compare[1]
1090
							equal = compare[0] == compare[1]
1060
						if not equal:
1091
						if not equal:
1061
							if isinstance(value, list):
1092
							ucs_object[ucs_key] = value
1062
								ucs_object[ucs_key] = list(set(value))
1063
							else:
1064
								ucs_object[ucs_key] = value
1065
							ud.debug(ud.LDAP, ud.INFO,
1093
							ud.debug(ud.LDAP, ud.INFO,
1066
											   "set key in ucs-object: %s" % ucs_key)
1094
											   "set key in ucs-object: %s" % ucs_key)
1067
				else:
1095
				else:
 Lines 1096-1113    Link Here 
1096
						else:
1124
						else:
1097
							ud.debug(ud.LDAP, ud.WARN, '__set_values: The attributes for %s have not been removed as it represents a mandatory attribute' % ucs_key)
1125
							ud.debug(ud.LDAP, ud.WARN, '__set_values: The attributes for %s have not been removed as it represents a mandatory attribute' % ucs_key)
1098
1126
1127
1099
		for attr_key in self.property[property_type].attributes.keys():
1128
		for attr_key in self.property[property_type].attributes.keys():
1100
			if self.property[property_type].attributes[attr_key].sync_mode in ['read', 'sync']:
1129
			if self.property[property_type].attributes[attr_key].sync_mode in ['read', 'sync']:
1130
				set_values(self.property[property_type].attributes[attr_key])
1101
1131
1102
				con_attribute = self.property[property_type].attributes[attr_key].con_attribute
1103
				con_other_attribute = self.property[property_type].attributes[attr_key].con_other_attribute
1104
1105
				if not object.get('changed_attributes') or con_attribute in object.get('changed_attributes') or (con_other_attribute and con_other_attribute in object.get('changed_attributes')):
1106
					ud.debug(ud.LDAP, ud.INFO, '__set_values: Set: %s' % con_attribute)
1107
					set_values(self.property[property_type].attributes[attr_key])
1108
				else:
1109
					ud.debug(ud.LDAP, ud.INFO, '__set_values: Skip: %s' % con_attribute)
1110
1111
		# post-values
1132
		# post-values
1112
		if not self.property[property_type].post_attributes:
1133
		if not self.property[property_type].post_attributes:
1113
			return
1134
			return
 Lines 1118-1138    Link Here 
1118
					set_values(self.property[property_type].post_attributes[attr_key].mapping[1](self, property_type, object))
1139
					set_values(self.property[property_type].post_attributes[attr_key].mapping[1](self, property_type, object))
1119
			else:
1140
			else:
1120
				if self.property[property_type].post_attributes[attr_key].sync_mode in ['read', 'sync']:
1141
				if self.property[property_type].post_attributes[attr_key].sync_mode in ['read', 'sync']:
1121
1142
					if self.property[property_type].post_attributes[attr_key].reverse_attribute_check:
1122
					con_attribute = self.property[property_type].post_attributes[attr_key].con_attribute
1143
						if object['attributes'].get(self.property[property_type].post_attributes[attr_key].ldap_attribute):
1123
					con_other_attribute = self.property[property_type].post_attributes[attr_key].con_other_attribute
1144
							set_values(self.property[property_type].post_attributes[attr_key])
1124
1125
					if not object.get('changed_attributes') or con_attribute in object.get('changed_attributes') or (con_other_attribute and con_other_attribute in object.get('changed_attributes')):
1126
						ud.debug(ud.LDAP, ud.INFO, '__set_values: Set: %s' % con_attribute)
1127
						if self.property[property_type].post_attributes[attr_key].reverse_attribute_check:
1128
							if object['attributes'].get(self.property[property_type].post_attributes[attr_key].ldap_attribute):
1129
								set_values(self.property[property_type].post_attributes[attr_key])
1130
							else:
1131
								ucs_object[self.property[property_type].post_attributes[attr_key].ucs_attribute] = ''
1132
						else:
1145
						else:
1133
							set_values(self.property[property_type].post_attributes[attr_key])
1146
							ucs_object[self.property[property_type].post_attributes[attr_key].ucs_attribute] = ''
1134
					else:
1147
					else:
1135
						ud.debug(ud.LDAP, ud.INFO, '__set_values: Skip: %s' % con_attribute)
1148
						set_values(self.property[property_type].post_attributes[attr_key])
1136
1149
1137
	def __modify_custom_attributes(self, property_type, object, ucs_object, module, position, modtype = "modify"):
1150
	def __modify_custom_attributes(self, property_type, object, ucs_object, module, position, modtype = "modify"):
1138
		if object.has_key('custom_attributes'):
1151
		if object.has_key('custom_attributes'):
 Lines 1207-1239    Link Here 
1207
		ucs_object.move(object['dn'])
1220
		ucs_object.move(object['dn'])
1208
		return True
1221
		return True
1209
1222
1210
	def _get_entryUUID(self, dn):
1211
		try:
1212
			result = self.search_ucs(base=dn, scope='base', attr=['entryUUID'], unique=True)
1213
			if result:
1214
				return result[0][1].get('entryUUID')[0]
1215
			else:
1216
				return None
1217
		except univention.admin.uexceptions.noObject:
1218
			return None
1219
1220
	def update_deleted_cache_after_removal_in_ucs(self, entryUUID, objectGUID):
1221
		if not entryUUID:
1222
			return
1223
		# use a dummy value
1224
		if not objectGUID:
1225
			objectGUID='objectGUID'
1226
		ud.debug(ud.LDAP, ud.INFO, "update_deleted_cache_after_removal_in_ucs: Save entryUUID %s as deleted to UCS deleted cache. ObjectGUUID: %s" % (entryUUID, objectGUID))
1227
		self._set_config_option('UCS deleted', entryUUID, base64.encodestring(objectGUID))
1228
1229
	def was_entryUUID_deleted(self, entryUUID):
1230
		objectGUID = self.config.get('UCS deleted', entryUUID)
1231
		if objectGUID:
1232
			return True
1233
		else:
1234
			return False
1235
			
1236
			
1237
	def delete_in_ucs(self, property_type, object, module, position):
1223
	def delete_in_ucs(self, property_type, object, module, position):
1238
		_d=ud.function('ldap.delete_in_ucs')		
1224
		_d=ud.function('ldap.delete_in_ucs')		
1239
1225
 Lines 1241-1249    Link Here 
1241
			ud.debug(ud.LDAP, ud.PROCESS, "Delete of %s was disabled in mapping" % object['dn'])
1227
			ud.debug(ud.LDAP, ud.PROCESS, "Delete of %s was disabled in mapping" % object['dn'])
1242
			return True
1228
			return True
1243
1229
1244
		objectGUID = object['attributes'].get('objectGUID')[0]
1245
		entryUUID = self._get_entryUUID(object['dn'])
1246
1247
		module = self.modules[property_type]
1230
		module = self.modules[property_type]
1248
		ucs_object = univention.admin.objects.get(module, None, self.lo, dn=object['dn'], position='')
1231
		ucs_object = univention.admin.objects.get(module, None, self.lo, dn=object['dn'], position='')
1249
1232
 Lines 1250-1256    Link Here 
1250
		try:
1233
		try:
1251
			ucs_object.open()
1234
			ucs_object.open()
1252
			ucs_object.remove()
1235
			ucs_object.remove()
1253
			self. update_deleted_cache_after_removal_in_ucs(entryUUID, objectGUID)
1254
			return True
1236
			return True
1255
		except Exception, e:
1237
		except Exception, e:
1256
			ud.debug(ud.LDAP, ud.INFO,"delete object exception: %s"%e)
1238
			ud.debug(ud.LDAP, ud.INFO,"delete object exception: %s"%e)
 Lines 1285-1291    Link Here 
1285
			else:
1267
			else:
1286
				raise
1268
				raise
1287
1269
1288
	def sync_to_ucs(self, property_type, object, premapped_s4_dn, original_object):
1270
	def sync_to_ucs(self, property_type, object, premapped_s4_dn):
1289
		_d=ud.function('ldap.sync_to_ucs')
1271
		_d=ud.function('ldap.sync_to_ucs')
1290
		# this function gets an object from the s4 class, which should be converted into a ucs modul
1272
		# this function gets an object from the s4 class, which should be converted into a ucs modul
1291
1273
 Lines 1329-1353    Link Here 
1329
				pass
1311
				pass
1330
1312
1331
		try:
1313
		try:
1332
			guid = original_object.get('attributes').get('objectGUID')[0]
1333
1334
			object['changed_attributes'] = []
1335
			if object['modtype'] == 'modify' and original_object:
1336
				old_s4_object = self.s4cache.get_entry(guid)
1337
				ud.debug(ud.LDAP, ud.INFO, "sync_to_ucs: old_s4_object: %s" % old_s4_object)
1338
				ud.debug(ud.LDAP, ud.INFO, "sync_to_ucs: new_s4_object: %s" % original_object['attributes'])
1339
				if old_s4_object:
1340
					for attr in original_object['attributes']:
1341
						if old_s4_object.get(attr) != original_object['attributes'].get(attr):
1342
							object['changed_attributes'].append(attr)
1343
					for attr in old_s4_object:
1344
						if old_s4_object.get(attr) != original_object['attributes'].get(attr):
1345
							if not attr in object['changed_attributes']:
1346
								object['changed_attributes'].append(attr)
1347
				else:
1348
					object['changed_attributes'] = original_object['attributes'].keys()
1349
			ud.debug(ud.LDAP, ud.INFO, "The following attributes have been changed: %s" % object['changed_attributes'])
1350
						
1351
			result = False
1314
			result = False
1352
			if hasattr(self.property[property_type],"ucs_sync_function"):
1315
			if hasattr(self.property[property_type],"ucs_sync_function"):
1353
				result = self.property[property_type].ucs_sync_function(self, property_type, object)
1316
				result = self.property[property_type].ucs_sync_function(self, property_type, object)
 Lines 1355-1361    Link Here 
1355
				if object['modtype'] == 'add':
1318
				if object['modtype'] == 'add':
1356
					result = self.add_in_ucs(property_type, object, module, position)
1319
					result = self.add_in_ucs(property_type, object, module, position)
1357
					self._check_dn_mapping(object['dn'], premapped_s4_dn)
1320
					self._check_dn_mapping(object['dn'], premapped_s4_dn)
1358
					self.s4cache.add_entry(guid, original_object.get('attributes'))
1359
				if object['modtype'] == 'delete':
1321
				if object['modtype'] == 'delete':
1360
					if not old_object:
1322
					if not old_object:
1361
						ud.debug(ud.LDAP, ud.WARN,
1323
						ud.debug(ud.LDAP, ud.WARN,
 Lines 1364-1380    Link Here 
1364
					else:
1326
					else:
1365
						result = self.delete_in_ucs(property_type, object, module, position)
1327
						result = self.delete_in_ucs(property_type, object, module, position)
1366
					self._remove_dn_mapping(object['dn'], premapped_s4_dn)
1328
					self._remove_dn_mapping(object['dn'], premapped_s4_dn)
1367
					self.s4cache.remove_entry(guid)
1368
				if object['modtype'] == 'move':
1329
				if object['modtype'] == 'move':
1369
					result = self.move_in_ucs(property_type, object, module, position)
1330
					result = self.move_in_ucs(property_type, object, module, position)
1370
					self._remove_dn_mapping(object['olddn'],  '') # we don't know the old s4-dn here anymore, will be checked by remove_dn_mapping
1331
					self._remove_dn_mapping(object['olddn'],  '') # we don't know the old s4-dn here anymore, will be checked by remove_dn_mapping
1371
					self._check_dn_mapping(object['dn'], premapped_s4_dn)
1332
					self._check_dn_mapping(object['dn'], premapped_s4_dn)
1372
					# Check S4cache
1373
1333
1374
				if object['modtype'] == 'modify':
1334
				if object['modtype'] == 'modify':
1375
					result = self.modify_in_ucs(property_type, object, module, position)
1335
					result = self.modify_in_ucs(property_type, object, module, position)
1376
					self._check_dn_mapping(object['dn'], premapped_s4_dn)
1336
					self._check_dn_mapping(object['dn'], premapped_s4_dn)
1377
					self.s4cache.add_entry(guid, original_object.get('attributes'))
1378
					
1337
					
1379
			if not result:
1338
			if not result:
1380
				ud.debug(ud.LDAP, ud.WARN,
1339
				ud.debug(ud.LDAP, ud.WARN,
 Lines 1413-1419    Link Here 
1413
			self._debug_traceback(ud.ERROR, "Unknown Exception during sync_to_ucs")
1372
			self._debug_traceback(ud.ERROR, "Unknown Exception during sync_to_ucs")
1414
			return False
1373
			return False
1415
1374
1416
	def sync_from_ucs(self, property_type, object, pre_mapped_ucs_dn, old_dn=None, old_ucs_object = None, new_ucs_object = None):
1375
	def sync_from_ucs(self, property_type, object, old_dn=None):
1417
		# dummy
1376
		# dummy
1418
		return False
1377
		return False
1419
1378
(-)modules/univention/s4connector/s4/__init__.py (-117 / +69 lines)
 Lines 2040-2046    Link Here 
2040
						mapped_object = self._object_mapping(property_key,object)
2040
						mapped_object = self._object_mapping(property_key,object)
2041
						try:
2041
						try:
2042
							if not self._ignore_object(property_key,mapped_object) and not self._ignore_object(property_key,object):
2042
							if not self._ignore_object(property_key,mapped_object) and not self._ignore_object(property_key,object):
2043
								sync_successfull = self.sync_to_ucs(property_key, mapped_object, premapped_s4_dn, object)
2043
								sync_successfull = self.sync_to_ucs(property_key, mapped_object, premapped_s4_dn)
2044
							else:
2044
							else:
2045
								sync_successfull = True
2045
								sync_successfull = True
2046
						except (ldap.SERVER_DOWN, SystemExit):
2046
						except (ldap.SERVER_DOWN, SystemExit):
 Lines 2134-2140    Link Here 
2134
					try:
2134
					try:
2135
						mapped_object = self._object_mapping(property_key,object)
2135
						mapped_object = self._object_mapping(property_key,object)
2136
						if not self._ignore_object(property_key,mapped_object):
2136
						if not self._ignore_object(property_key,mapped_object):
2137
							sync_successfull = self.sync_to_ucs(property_key, mapped_object, object['dn'], object)
2137
							sync_successfull = self.sync_to_ucs(property_key, mapped_object, object['dn'])
2138
						else:
2138
						else:
2139
							sync_successfull = True
2139
							sync_successfull = True
2140
					except (ldap.SERVER_DOWN, SystemExit):
2140
					except (ldap.SERVER_DOWN, SystemExit):
 Lines 2204-2214    Link Here 
2204
		sys.stdout.flush()
2204
		sys.stdout.flush()
2205
		return change_count
2205
		return change_count
2206
2206
2207
	def __has_attribute_value_changed(self, attribute, old_ucs_object, new_ucs_object):
2208
		return not old_ucs_object.get(attribute) == new_ucs_object.get(attribute)
2209
2207
2210
2208
	def sync_from_ucs(self, property_type, object, pre_mapped_ucs_dn, old_dn=None, old_ucs_object = None):
2211
	def sync_from_ucs(self, property_type, object, pre_mapped_ucs_dn, old_dn=None, old_ucs_object = None, new_ucs_object = None):
2212
		_d=ud.function('ldap.__sync_from_ucs')
2209
		_d=ud.function('ldap.__sync_from_ucs')
2213
		# Diese Methode erhaelt von der UCS Klasse ein Objekt,
2210
		# Diese Methode erhaelt von der UCS Klasse ein Objekt,
2214
		# welches hier bearbeitet wird und in das S4 geschrieben wird.
2211
		# welches hier bearbeitet wird und in das S4 geschrieben wird.
 Lines 2273-2281    Link Here 
2273
2270
2274
		s4_object=self.get_object(object['dn'])
2271
		s4_object=self.get_object(object['dn'])
2275
2272
2276
		#
2277
		# ADD
2278
		#
2279
		if (object['modtype'] == 'add' and not s4_object) or (object['modtype'] == 'modify' and not s4_object):
2273
		if (object['modtype'] == 'add' and not s4_object) or (object['modtype'] == 'modify' and not s4_object):
2280
			ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: add object: %s"%object['dn'])
2274
			ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: add object: %s"%object['dn'])
2281
2275
 Lines 2355-2477    Link Here 
2355
						f(self, property_type, object)
2349
						f(self, property_type, object)
2356
						ud.debug(ud.LDAP, ud.INFO, "Call post_con_modify_functions: %s (done)" % f)
2350
						ud.debug(ud.LDAP, ud.INFO, "Call post_con_modify_functions: %s (done)" % f)
2357
2351
2358
		#
2359
		# MODIFY
2360
		#
2361
		elif (object['modtype'] == 'modify' and s4_object) or (object['modtype'] == 'add' and s4_object):
2352
		elif (object['modtype'] == 'modify' and s4_object) or (object['modtype'] == 'add' and s4_object):
2362
			ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: modify object: %s"%object['dn'])
2353
			ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: modify object: %s"%object['dn'])
2363
			ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: old_object: %s" % old_ucs_object)
2364
			ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: new_object: %s" % new_ucs_object)
2365
			object['old_ucs_object'] = old_ucs_object
2366
			object['new_ucs_object'] = new_ucs_object
2367
			attribute_list = set(old_ucs_object.keys()).union(set(new_ucs_object.keys()))
2368
			if hasattr(self.property[property_type],"con_sync_function"):
2354
			if hasattr(self.property[property_type],"con_sync_function"):
2369
				self.property[property_type].con_sync_function(self, property_type, object)
2355
				self.property[property_type].con_sync_function(self, property_type, object)
2370
			else:
2356
			else:
2371
				# Iterate over attributes and post_attributes
2357
				attr_list = []
2372
				for attribute_type_name, attribute_type in [('attributes', self.property[property_type].attributes),('post_attributes', self.property[property_type].post_attributes)]:
2358
				if hasattr(self.property[property_type], 'attributes') and self.property[property_type].attributes != None:
2373
					if hasattr(self.property[property_type], attribute_type_name) and attribute_type != None:
2359
					for attr,value in object['attributes'].items():
2374
						for attr in attribute_list:
2360
						attr_list.append(attr)
2375
							value = new_ucs_object.get(attr)
2361
						for attribute in self.property[property_type].attributes.keys():
2376
							if not self.__has_attribute_value_changed(attr, old_ucs_object, new_ucs_object):
2362
							if self.property[property_type].attributes[attribute].con_attribute == attr or self.property[property_type].attributes[attribute].con_other_attribute == attr:
2377
								continue
2363
								if not s4_object.has_key(attr):
2378
2364
									if value:
2379
							ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: The following attribute has been changed: %s" % attr)
2365
										modlist.append((ldap.MOD_ADD, attr, value))
2380
			
2366
								elif self.property[property_type].attributes[attribute].compare_function:
2381
							for attribute in attribute_type.keys():
2367
									if not self.property[property_type].attributes[attribute].compare_function(value,s4_object[attr]):
2382
								if attribute_type[attribute].ldap_attribute == attr:
2368
										modlist.append((ldap.MOD_REPLACE, attr, value))
2383
									ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: Found a corresponding mapping defintion: %s" % attribute)
2369
								elif not univention.s4connector.compare_lowercase(value,s4_object[attr]): # FIXME: use defined compare-function from mapping.py
2384
2370
									modlist.append((ldap.MOD_REPLACE, attr, value))
2385
									s4_attribute = attribute_type[attribute].con_attribute
2371
				if hasattr(self.property[property_type], 'post_attributes') and self.property[property_type].post_attributes != None:
2386
									s4_other_attribute = attribute_type[attribute].con_other_attribute
2372
					for attr,value in object['attributes'].items():
2387
2373
						attr_list.append(attr)
2388
									if not attribute_type[attribute].sync_mode in ['write', 'sync']:
2374
						for attribute in self.property[property_type].post_attributes.keys():
2389
										ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: %s is in not in wroite or sync mode. Skipping" % attribute)
2375
							if self.property[property_type].post_attributes[attribute].con_attribute == attr or self.property[property_type].post_attributes[attribute].con_other_attribute == attr:
2376
								if self.property[property_type].post_attributes[attribute].reverse_attribute_check:
2377
									if not object['attributes'].get(self.property[property_type].post_attributes[attribute].ldap_attribute):
2390
										continue
2378
										continue
2379
								if not s4_object.has_key(attr):
2380
									if value:
2381
										modlist.append((ldap.MOD_ADD, attr, value))
2382
								elif self.property[property_type].post_attributes[attribute].compare_function:
2383
									if not self.property[property_type].post_attributes[attribute].compare_function(value,s4_object[attr]):
2384
										modlist.append((ldap.MOD_REPLACE, attr, value))
2385
								elif not univention.s4connector.compare_lowercase(value,s4_object[attr]): # FIXME: use defined compare-function from mapping.py
2386
									modlist.append((ldap.MOD_REPLACE, attr, value))
2391
2387
2392
									modify = False
2388
				attrs_in_current_ucs_object = object['attributes'].keys()
2389
				attrs_which_should_be_mapped = []
2390
				attrs_to_remove_from_s4_object = []
2393
2391
2394
									# Get the UCS attributes
2392
				if hasattr(self.property[property_type], 'attributes') and self.property[property_type].attributes != None:
2395
									old_values = set(old_ucs_object.get(attr, []))
2393
					for ac in self.property[property_type].attributes.keys():
2396
									new_values = set(new_ucs_object.get(attr, []))
2394
						if self.property[property_type].attributes[ac].sync_mode in ['write', 'sync']:
2395
							if not self.property[property_type].attributes[ac].con_attribute in attrs_which_should_be_mapped:
2396
								attrs_which_should_be_mapped.append(self.property[property_type].attributes[ac].con_attribute)
2397
							if self.property[property_type].attributes[ac].con_other_attribute:
2398
								if not self.property[property_type].attributes[ac].con_other_attribute in attrs_which_should_be_mapped:
2399
									attrs_which_should_be_mapped.append(self.property[property_type].attributes[ac].con_other_attribute)
2397
2400
2398
									ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: old_values: %s" % old_values)
2401
				if hasattr(self.property[property_type], 'post_attributes') and self.property[property_type].post_attributes != None:
2399
									ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: new_values: %s" % new_values)
2402
					for ac in self.property[property_type].post_attributes.keys():
2403
						if self.property[property_type].post_attributes[ac].sync_mode in ['write', 'sync']:
2404
							if not self.property[property_type].post_attributes[ac].con_attribute in attrs_which_should_be_mapped:
2405
								if self.property[property_type].post_attributes[ac].reverse_attribute_check:
2406
									if object['attributes'].get(self.property[property_type].post_attributes[ac].ldap_attribute):
2407
										attrs_which_should_be_mapped.append(self.property[property_type].post_attributes[ac].con_attribute)
2408
									elif s4_object.get(self.property[property_type].post_attributes[ac].con_attribute):
2409
										modlist.append((ldap.MOD_DELETE, self.property[property_type].post_attributes[ac].con_attribute, None))
2410
								else:
2411
									attrs_which_should_be_mapped.append(self.property[property_type].post_attributes[ac].con_attribute)
2412
							if self.property[property_type].post_attributes[ac].con_other_attribute:
2413
								if not self.property[property_type].post_attributes[ac].con_other_attribute in attrs_which_should_be_mapped:
2414
									attrs_which_should_be_mapped.append(self.property[property_type].post_attributes[ac].con_other_attribute)
2400
2415
2401
									if attribute_type[attribute].compare_function:
2416
				modlist_empty_attrs = []			
2402
										if not attribute_type[attribute].compare_function(list(old_values), list(new_values)):
2417
				for expected_attribute in attrs_which_should_be_mapped:
2403
											modify = True
2418
					if not object['attributes'].has_key(expected_attribute):
2404
									elif not univention.s4connector.compare_lowercase(list(old_values), list(new_values)): # FIXME: use defined compare-function from mapping.py
2419
						attrs_to_remove_from_s4_object.append(expected_attribute)
2405
										modify=True
2406
2420
2407
									if not modify:
2421
					if modlist:
2408
										ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: no modification necessary for %s" % attribute)
2422
						for modified_attrs in modlist:
2423
							if modified_attrs[1] in attrs_to_remove_from_s4_object and len(modified_attrs[2]) > 0:
2424
								attrs_to_remove_from_s4_object.remove(modified_attrs[1])
2409
2425
2410
									if modify:
2426
				for yank_empty_attr in attrs_to_remove_from_s4_object:
2411
										# So, at this point we have the old and the new UCS object.
2427
					if s4_object.has_key(yank_empty_attr):
2412
										# Thus we can create the diff, but we have to check the current S4 object
2428
						if value != None:
2429
							modlist.append((ldap.MOD_DELETE, yank_empty_attr, None))
2413
2430
2414
										if not old_values:
2415
											to_add = new_values
2416
											to_remove = set([])
2417
										elif not new_values:
2418
											to_remove = old_values
2419
											to_add = set([])
2420
										else:
2421
											to_add = new_values - old_values
2422
											to_remove = old_values - new_values
2423
2424
										if s4_other_attribute:
2425
											# in this case we need lists because sets are unorded and the order is important
2426
											current_s4_values = set(s4_object.get(s4_attribute, []))
2427
											ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: The current S4 values: %s" % current_s4_values)
2428
2429
											current_s4_other_values = set(s4_object.get(attribute_type[attribute].con_other_attribute, []))
2430
											ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: The current S4 other values: %s" % current_s4_other_values)
2431
2432
											new_s4_values = current_s4_values - to_remove
2433
											if not new_s4_values and to_add:
2434
												for n_value in new_ucs_object.get(attr, []):
2435
													if n_value in to_add:
2436
														to_add = to_add - set([n_value])
2437
														new_s4_values = [n_value]
2438
														break
2439
2440
											new_s4_other_values = (current_s4_other_values | to_add) - to_remove
2441
											if current_s4_values != new_s4_values:
2442
												if new_s4_values:
2443
													modlist.append((ldap.MOD_REPLACE, s4_attribute, new_s4_values))
2444
												else:
2445
													modlist.append((ldap.MOD_REPLACE, s4_attribute, []))
2446
2447
											if current_s4_other_values != new_s4_other_values:
2448
												modlist.append((ldap.MOD_REPLACE, s4_other_attribute, new_s4_other_values))
2449
										else:
2450
											current_s4_values = set(s4_object.get(s4_attribute, []))
2451
2452
											ud.debug(ud.LDAP, ud.INFO, "sync_from_ucs: The current S4 values: %s" % current_s4_values)
2453
2454
											if (to_add or to_remove) and attribute_type[attribute].single_value:
2455
												modify=False
2456
												if not current_s4_values or not value:
2457
													modify=True
2458
												elif attribute_type[attribute].compare_function:
2459
													if not attribute_type[attribute].compare_function(list(current_s4_values), list(value)):
2460
														modify=True
2461
												elif not univention.s4connector.compare_lowercase(list(current_s4_values), list(value)):
2462
													modify=True
2463
												if modify:
2464
													modlist.append((ldap.MOD_REPLACE, s4_attribute, value))
2465
											else:
2466
												if to_remove:
2467
													r = current_s4_values & to_remove
2468
													if r:
2469
														modlist.append((ldap.MOD_DELETE, s4_attribute, r))
2470
												if to_add:
2471
													a = to_add - current_s4_values
2472
													if a:
2473
														modlist.append((ldap.MOD_ADD, s4_attribute, a))
2474
2475
				ud.debug(ud.LDAP, ud.INFO, "to modify: %s" % object['dn'])
2431
				ud.debug(ud.LDAP, ud.INFO, "to modify: %s" % object['dn'])
2476
				if modlist:
2432
				if modlist:
2477
					ud.debug(ud.LDAP, ud.ALL, "sync_from_ucs: modlist: %s" % modlist)
2433
					ud.debug(ud.LDAP, ud.ALL, "sync_from_ucs: modlist: %s" % modlist)
 Lines 2488-2496    Link Here 
2488
						ud.debug(ud.LDAP, ud.INFO, "Call post_con_modify_functions: %s" % f)
2444
						ud.debug(ud.LDAP, ud.INFO, "Call post_con_modify_functions: %s" % f)
2489
						f(self, property_type, object)
2445
						f(self, property_type, object)
2490
						ud.debug(ud.LDAP, ud.INFO, "Call post_con_modify_functions: %s (done)" % f)
2446
						ud.debug(ud.LDAP, ud.INFO, "Call post_con_modify_functions: %s (done)" % f)
2491
		#
2492
		# DELETE
2493
		#
2494
		elif object['modtype'] == 'delete':
2447
		elif object['modtype'] == 'delete':
2495
			if hasattr(self.property[property_type],"con_sync_function"):
2448
			if hasattr(self.property[property_type],"con_sync_function"):
2496
				self.property[property_type].con_sync_function(self, property_type, object)
2449
				self.property[property_type].con_sync_function(self, property_type, object)
 Lines 2513-2519    Link Here 
2513
	def delete_in_s4(self, object, property_type ):
2466
	def delete_in_s4(self, object, property_type ):
2514
		_d=ud.function('ldap.delete_in_s4')
2467
		_d=ud.function('ldap.delete_in_s4')
2515
		ud.debug(ud.LDAP, ud.ALL,"delete: %s" % object['dn'])
2468
		ud.debug(ud.LDAP, ud.ALL,"delete: %s" % object['dn'])
2516
		ud.debug(ud.LDAP, ud.ALL,"delete_in_s4: %s" % object)
2517
		try:
2469
		try:
2518
			self.lo_s4.lo.delete_s(compatible_modstring(object['dn']))
2470
			self.lo_s4.lo.delete_s(compatible_modstring(object['dn']))
2519
		except ldap.NO_SUCH_OBJECT:
2471
		except ldap.NO_SUCH_OBJECT:
(-)modules/univention/s4connector/s4/password.py (-23 lines)
 Lines 457-480    Link Here 
457
	_d=ud.function('ldap.s4.password_sync_ucs_to_s4')
457
	_d=ud.function('ldap.s4.password_sync_ucs_to_s4')
458
	ud.debug(ud.LDAP, ud.INFO, "password_sync_ucs_to_s4 called")
458
	ud.debug(ud.LDAP, ud.INFO, "password_sync_ucs_to_s4 called")
459
	
459
	
460
	modify=False
461
	old_ucs_object = object.get('old_ucs_object', {})
462
	new_ucs_object = object.get('new_ucs_object', {})
463
	if old_ucs_object or new_ucs_object:
464
		for attr in ['sambaLMPassword', 'sambaNTPassword','sambaPwdLastSet','sambaPwdMustChange', 'krb5PrincipalName', 'krb5Key', 'shadowLastChange', 'shadowMax', 'krb5PasswordEnd', 'univentionService']:
465
			old_values = set(old_ucs_object.get(attr, []))
466
			new_values = set(new_ucs_object.get(attr, []))
467
			if old_values != new_values:
468
				modify=True
469
				break
470
	else:
471
		# add mode
472
		modify=True
473
474
	if not modify:
475
		ud.debug(ud.LDAP, ud.INFO, 'password_sync_ucs_to_s4: the password for %s has not been changed. Skipping password sync.' % (object['dn']))
476
		return
477
478
	compatible_modstring = univention.s4connector.s4.compatible_modstring
460
	compatible_modstring = univention.s4connector.s4.compatible_modstring
479
	try:
461
	try:
480
		ud.debug(ud.LDAP, ud.INFO, "Object DN=%s" % object['dn'])
462
		ud.debug(ud.LDAP, ud.INFO, "Object DN=%s" % object['dn'])
 Lines 645-655    Link Here 
645
	_d=ud.function('ldap.s4.password_sync_s4_to_ucs')
627
	_d=ud.function('ldap.s4.password_sync_s4_to_ucs')
646
	ud.debug(ud.LDAP, ud.INFO, "password_sync_s4_to_ucs called")
628
	ud.debug(ud.LDAP, ud.INFO, "password_sync_s4_to_ucs called")
647
629
648
	if ucs_object['modtype'] == 'modify':
649
		if not 'pwdLastSet' in ucs_object.get('changed_attributes', []):
650
			ud.debug(ud.LDAP, ud.INFO, 'password_sync_s4_to_ucs: the password for %s has not been changed. Skipping password sync.' % (ucs_object['dn']))
651
			return
652
653
	object=s4connector._object_mapping(key, ucs_object, 'ucs')
630
	object=s4connector._object_mapping(key, ucs_object, 'ucs')
654
	res=s4connector.lo_s4.lo.search_s(univention.s4connector.s4.compatible_modstring(object['dn']), ldap.SCOPE_BASE, '(objectClass=*)',['objectSid','pwdLastSet'])
631
	res=s4connector.lo_s4.lo.search_s(univention.s4connector.s4.compatible_modstring(object['dn']), ldap.SCOPE_BASE, '(objectClass=*)',['objectSid','pwdLastSet'])
655
632
(-)modules/univention/s4connector/s4cache.py (-405 lines)
 Lines 1-405    Link Here 
1
#!/usr/bin/python2.6
2
# -*- coding: utf-8 -*-
3
#
4
# Univention S4 Connector
5
#  s4 cache
6
#
7
# Copyright 2014 Univention GmbH
8
#
9
# http://www.univention.de/
10
#
11
# All rights reserved.
12
#
13
# The source code of this program is made available
14
# under the terms of the GNU Affero General Public License version 3
15
# (GNU AGPL V3) as published by the Free Software Foundation.
16
#
17
# Binary versions of this program provided by Univention to you as
18
# well as other copyrighted, protected or trademarked materials like
19
# Logos, graphics, fonts, specific documentations and configurations,
20
# cryptographic keys etc. are subject to a license agreement between
21
# you and Univention and not subject to the GNU AGPL V3.
22
#
23
# In the case you use this program under the terms of the GNU AGPL V3,
24
# the program is provided in the hope that it will be useful,
25
# but WITHOUT ANY WARRANTY; without even the implied warranty of
26
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
# GNU Affero General Public License for more details.
28
#
29
# You should have received a copy of the GNU Affero General Public
30
# License with the Debian GNU/Linux or Univention distribution in file
31
# /usr/share/common-licenses/AGPL-3; if not, see
32
# <http://www.gnu.org/licenses/>.
33
34
import univention.debug2 as ud
35
import sqlite3
36
import inspect
37
import base64
38
import binascii
39
40
41
def func_name():
42
	return inspect.currentframe().f_back.f_code.co_name
43
44
def _is_base64(val):
45
	try:
46
		# It is not sufficient to run base64.decodestring to detect a base64 string.
47
		# When the ascii decode is not possible, it is not a base4 string.
48
		val.decode('ascii')
49
	except UnicodeDecodeError:
50
		return False
51
	try:
52
		# The string must be casted as str otherwise we saw something like this:
53
		#	11.02.2014 03:53:44,141 LDAP        (INFO): _is_base64 returns True for: Í8^Ml%'<U+0097>A²ôâ/! ^RÃ
54
		#	11.02.2014 03:53:44,142 LDAP        (WARNING): S4Cache: sqlite: near "<U+0097>A²ôâ": syntax error. SQL command was: [u"SELECT id FROM GUIDS WHERE guid='\xcd8\rl%'\x97A\xb2\xf4\xe2/! \x12\xc3';"
55
		base64.decodestring(str(val))
56
		return True
57
	except binascii.Error:
58
		return False
59
60
def _decode_base64(val):
61
	return base64.decodestring(val)
62
63
def _encode_base64(val):
64
	return base64.encodestring(val)
65
66
def _encode_guid(guid):
67
	# guid may be unicode
68
69
	if _is_base64(guid):
70
		return guid
71
72
	if type(guid) == type(u''):
73
		return guid.encode('ISO-8859-1').encode('base64')
74
	else:
75
		return unicode(guid,'latin').encode('ISO-8859-1').encode('base64')
76
77
def _decode_guid(guid):
78
	try:
79
		return base64.decodestring(guid)
80
	except binascii.Error:
81
		return guid
82
83
84
class EntryDiff(object):
85
	def __init__(self, old, new):
86
		self.old = old
87
		self.new = new
88
		if not old:
89
			old = {}
90
		if not new:
91
			new = {}
92
		self.set_old = set(old.keys())
93
		self.set_new = set(new.keys())
94
		self.intersect = self.set_new.intersection(self.set_old)
95
96
	def added(self):
97
		return self.set_new - self.intersect
98
99
	def removed(self):
100
		return self.set_old - self.intersect
101
102
	def changed(self):
103
		return set(o for o in self.intersect if self.old[o] != self.new[o])
104
105
106
class S4Cache:
107
	"""
108
		Local cache for the current Samba 4 state of the s4connector.
109
		With this cache the connector has the possibility to create
110
		a diff between the new Samba 4 object and the old one from
111
		cache.
112
	"""
113
	def __init__ (self, filename):
114
		_d = ud.function('S4Cache.%s' % func_name())
115
		self.filename = filename
116
		self._dbcon = sqlite3.connect(self.filename)
117
		self.s4cache = {}
118
119
		self.__create_tables()
120
121
	def add_entry(self, guid, entry):
122
		_d = ud.function('S4Cache.%s' % func_name())
123
124
		guid = _encode_guid(guid).strip()
125
126
		if not self._guid_exists(guid):
127
			self._add_entry(guid, entry)
128
		else:
129
			self._update_entry(guid, entry)
130
		self.s4cache[guid] = entry
131
132
133
	def diff_entry(self, old_entry, new_entry):
134
		_d = ud.function('S4Cache.%s' % func_name())
135
136
		result = {'added': None, 'removed': None, 'changed': None}
137
138
		diff = EntryDiff(old_entry, new_entry)
139
140
		result['added'] = diff.added()
141
		result['removed'] = diff.removed()
142
		result['changed'] = diff.changed()
143
144
		return result
145
146
	def get_entry(self, guid):
147
		_d = ud.function('S4Cache.%s' % func_name())
148
149
		entry = {}
150
151
		guid = _encode_guid(guid)
152
153
		guid_id = self._get_guid_id(guid)
154
155
		if not guid_id:
156
			return None
157
158
		sql_commands = [
159
			"SELECT ATTRIBUTES.attribute,data.value from data \
160
					inner join ATTRIBUTES ON data.attribute_id=attributes.id where guid_id = %s;" % (guid_id)
161
		]
162
163
		rows = self.__execute_sql_commands(sql_commands, fetch_result=True)
164
165
		if not rows:
166
			return None
167
168
		for line in rows:
169
			if not entry.get(line[0]):
170
				entry[str(line[0])] = []
171
			entry[line[0]].append(_decode_base64(line[1]))
172
173
		return entry
174
175
	def remove_entry(self, guid):
176
		_d = ud.function('S4Cache.%s' % func_name())
177
178
		guid = _encode_guid(guid)
179
180
		guid_id = self._get_guid_id(guid)
181
182
		if not guid_id:
183
			return None
184
185
		sql_commands = [
186
			"DELETE FROM data WHERE guid_id = '%(guid_id)s';" % ({'guid_id': guid_id}),
187
			"DELETE FROM guids WHERE id = '%(guid_id)s';" % ({'guid_id': guid_id})
188
		]
189
190
		self.__execute_sql_commands(sql_commands, fetch_result=False)
191
192
	def __execute_sql_commands(self, sql_commands, fetch_result=False):
193
		for i in [1, 2]:
194
			try:
195
				cur = self._dbcon.cursor()
196
				for sql_command in sql_commands:
197
					if isinstance(sql_command, tuple):
198
						ud.debug(ud.LDAP, ud.INFO, "S4Cache: Execute SQL command: '%s', '%s'" % (sql_command[0], sql_command[1]))
199
						cur.execute(sql_command[0], sql_command[1])
200
					else:
201
						ud.debug(ud.LDAP, ud.INFO, "S4Cache: Execute SQL command: '%s'" % sql_command)
202
						cur.execute(sql_command)
203
				self._dbcon.commit()
204
				if fetch_result:
205
					rows = cur.fetchall()
206
				cur.close()
207
				if fetch_result:
208
					ud.debug(ud.LDAP, ud.INFO, "S4Cache: Return SQL result: '%s'" % rows)
209
					return rows
210
				return None
211
			except sqlite3.Error, exp:
212
				ud.debug(ud.LDAP, ud.WARN, "S4Cache: sqlite: %s. SQL command was: %s" % (exp, sql_commands))
213
				if self._dbcon:
214
					self._dbcon.close()
215
				self._dbcon = sqlite3.connect(self.filename)
216
217
218
	def __create_tables(self):
219
		_d = ud.function('S4Cache.%s' % func_name())
220
221
		sql_commands = [
222
			"CREATE TABLE IF NOT EXISTS GUIDS (id INTEGER PRIMARY KEY, guid TEXT);",
223
			"CREATE TABLE IF NOT EXISTS ATTRIBUTES (id INTEGER PRIMARY KEY, attribute TEXT);",
224
			"CREATE TABLE IF NOT EXISTS DATA (id INTEGER PRIMARY KEY, guid_id INTEGER, attribute_id INTEGER, value TEXT);"
225
		]
226
227
		self.__execute_sql_commands(sql_commands, fetch_result=False)
228
229
230
	def _guid_exists(self, guid):
231
		_d = ud.function('S4Cache.%s' % func_name())
232
233
		return self._get_guid_id(guid.strip()) != None
234
235
236
	def _get_guid_id(self, guid):
237
		_d = ud.function('S4Cache.%s' % func_name())
238
239
		sql_commands = [
240
			"SELECT id FROM GUIDS WHERE guid='%s';" % (_encode_guid(guid).strip())
241
		]
242
243
		rows = self.__execute_sql_commands(sql_commands, fetch_result=True)
244
245
		if rows:
246
			return rows[0][0]
247
248
		return None
249
250
251
	def _append_guid(self, guid):
252
		_d = ud.function('S4Cache.%s' % func_name())
253
254
		sql_commands = [
255
			"INSERT INTO GUIDS(guid) VALUES('%s');" % (_encode_guid(guid).strip())
256
		]
257
258
		rows = self.__execute_sql_commands(sql_commands, fetch_result=False)
259
260
261
	def _get_attr_id(self, attr):
262
		_d = ud.function('S4Cache.%s' % func_name())
263
264
		sql_commands = [
265
			"SELECT id FROM ATTRIBUTES WHERE attribute='%s';" % (attr)
266
		]
267
268
		rows = self.__execute_sql_commands(sql_commands, fetch_result=True)
269
270
		if rows:
271
			return rows[0][0]
272
273
		return None
274
275
276
	def _attr_exists(self, guid):
277
		_d = ud.function('S4Cache.%s' % func_name())
278
279
		return self._get_attr_id(guid) != None
280
281
	def _create_attr(self, attr):
282
		_d = ud.function('S4Cache.%s' % func_name())
283
284
		sql_commands = [
285
			"INSERT INTO ATTRIBUTES(attribute) VALUES('%s');" % (attr)
286
		]
287
288
		self.__execute_sql_commands(sql_commands, fetch_result=False)
289
290
291
	def _get_attr_id_and_create_if_not_exists(self, attr):
292
		_d = ud.function('S4Cache.%s' % func_name())
293
		if not self._get_attr_id(attr):
294
			self._create_attr(attr)
295
296
		return self._get_attr_id(attr)
297
298
	def _add_entry(self, guid, entry):
299
		_d = ud.function('S4Cache.%s' % func_name())
300
301
		guid = guid.strip()
302
303
		self._append_guid(guid)
304
		guid_id = self._get_guid_id(guid)
305
306
		sql_commands = []
307
		for attr in entry.keys():
308
			attr_id = self._get_attr_id_and_create_if_not_exists(attr)
309
			for value in entry[attr]:
310
				sql_commands.append(
311
					(
312
						"INSERT INTO DATA(guid_id,attribute_id,value) VALUES(%s,%s,?);" % (guid_id, attr_id),
313
						[_encode_base64(value)]
314
					)
315
				)
316
317
		if sql_commands:
318
			self.__execute_sql_commands(sql_commands, fetch_result=False)
319
320
	def _update_entry(self, guid, entry):
321
		_d = ud.function('S4Cache.%s' % func_name())
322
323
		guid = guid.strip()
324
		guid_id = self._get_guid_id(guid)
325
		old_entry = self.get_entry(guid)
326
		diff = self.diff_entry(old_entry, entry)
327
328
		sql_commands = []
329
		for attribute in diff['removed']:
330
			sql_commands.append(
331
				"DELETE FROM data WHERE data.id IN (\
332
				SELECT data.id FROM DATA INNER JOIN ATTRIBUTES ON data.attribute_id=attributes.id \
333
					where attributes.attribute='%(attribute)s' and guid_id = '%(guid_id)s' \
334
				);" % ({'guid_id': guid_id, 'attribute': attribute})
335
			)
336
		for attribute in diff['added']:
337
			attr_id = self._get_attr_id_and_create_if_not_exists(attribute)
338
			for value in entry[attribute]:
339
				sql_commands.append(
340
					(
341
						"INSERT INTO DATA(guid_id,attribute_id,value) VALUES(%s,%s,?);" % (guid_id, attr_id),
342
						[_encode_base64(value)]
343
					)
344
				)
345
		for attribute in diff['changed']:
346
			attr_id = self._get_attr_id_and_create_if_not_exists(attribute)
347
			for value in set(old_entry.get(attribute)) - set(entry.get(attribute)):
348
				sql_commands.append(
349
					(
350
						"DELETE FROM data WHERE data.id IN (\
351
							SELECT data.id FROM DATA INNER JOIN ATTRIBUTES ON data.attribute_id=attributes.id \
352
							where attributes.id='%(attr_id)s' and guid_id = '%(guid_id)s' and value = ? \
353
						);" % ({'guid_id': guid_id, 'attr_id': attr_id}),
354
						[_encode_base64(value)]
355
					)
356
				)
357
			for value in set(entry.get(attribute)) - set(old_entry.get(attribute)):
358
				sql_commands.append(
359
					(
360
						"INSERT INTO DATA(guid_id,attribute_id,value) VALUES(%s,%s,?);" % (guid_id, attr_id),
361
						[_encode_base64(value)]
362
					)
363
				)
364
365
		if sql_commands:
366
			self.__execute_sql_commands(sql_commands, fetch_result=False)
367
368
369
370
if __name__ == '__main__':
371
372
	print 'Starting S4cache test example ',
373
374
	s4cache = S4Cache('cache.sqlite')
375
376
	guid = '1234'
377
378
	entry = {
379
		'attr1': ['foobar'],
380
		'attr2': [ 'val1', 'val2', 'val3']
381
	}
382
383
	s4cache.add_entry(guid, entry)
384
	entry_old = s4cache.get_entry(guid)
385
	diff_entry = s4cache.diff_entry(entry_old, entry)
386
	if diff_entry.get('changed') or diff_entry.get('removed') or diff_entry.get('added'):
387
		raise Exception('Test 1 failed: %s' % diff_entry)
388
	print '.',
389
390
	entry['attr3'] = ['val2']
391
	entry['attr2'] = ['val1', 'val3']
392
393
	diff_entry = s4cache.diff_entry(entry_old, entry)
394
	if diff_entry.get('changed') != set(['attr2']) or diff_entry.get('removed') or diff_entry.get('added') != set(['attr3']):
395
		raise Exception('Test 2 failed: %s' % diff_entry)
396
	print '.',
397
398
	s4cache.add_entry(guid, entry)
399
	entry_old = s4cache.get_entry(guid)
400
	diff_entry = s4cache.diff_entry(entry_old, entry)
401
	if diff_entry.get('changed') or diff_entry.get('removed') or diff_entry.get('added'):
402
		raise Exception('Test 3 failed: %s' % diff_entry)
403
	print '.',
404
405
	print ' done'

Return to bug 33621