From c69b4e86ef034251034f459676ac1088772df6ee Mon Sep 17 00:00:00 2001 From: Sebastien Robin <seb@nexedi.com> Date: Thu, 10 May 2007 12:13:52 +0000 Subject: [PATCH] fabien finished to implement authentication, and he added tests for it git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@14439 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5SyncML/Conduit/ERP5Conduit.py | 13 - product/ERP5SyncML/Publication.py | 43 ++- .../ERP5SyncML/PublicationSynchronization.py | 138 ++++++--- product/ERP5SyncML/Subscription.py | 163 +++++++++-- .../ERP5SyncML/SubscriptionSynchronization.py | 67 ++++- product/ERP5SyncML/SynchronizationTool.py | 220 +++++++++------ product/ERP5SyncML/XMLSyncUtils.py | 123 +++++---- .../ERP5SyncML/dtml/managePublications.dtml | 30 ++ .../ERP5SyncML/dtml/manageSubscriptions.dtml | 40 +++ .../dtml/manage_addPublication.dtml | 30 ++ .../dtml/manage_addSubscription.dtml | 39 +++ product/ERP5SyncML/tests/testERP5SyncML.py | 261 +++++++++++++++++- 12 files changed, 951 insertions(+), 216 deletions(-) diff --git a/product/ERP5SyncML/Conduit/ERP5Conduit.py b/product/ERP5SyncML/Conduit/ERP5Conduit.py index c347e2007b..ba617b6b59 100644 --- a/product/ERP5SyncML/Conduit/ERP5Conduit.py +++ b/product/ERP5SyncML/Conduit/ERP5Conduit.py @@ -129,11 +129,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): xml = self.convertToXml(xml) LOG('addNode',0,'xml_reconstitued: %s' % str(xml)) # In the case where this new node is a object to add - LOG('addNode',0,'object.id: %s' % object.getId()) - LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName) - LOG('addNode',0,'getSubObjectDepth: %i' % self.getSubObjectDepth(xml)) - LOG('addNode',0,'isHistoryAdd: %i' % self.isHistoryAdd(xml)) - LOG('addNode xml',0,repr(xml.toxml())) if xml.nodeName in self.XUPDATE_INSERT_OR_ADD and self.getSubObjectDepth(xml)==0: if self.isHistoryAdd(xml)!=-1: # bad hack XXX to be removed for element in self.getXupdateElementList(xml): @@ -216,7 +211,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): object_id = self.getAttribute(xml,'id') elif self.getSubObjectDepth(xml)==1: object_id = self.getSubObjectId(xml) - #LOG('ERP5Conduit',0,'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml)) elif self.getSubObjectDepth(xml)==2: # we have to call delete node on a subsubobject sub_object_id = self.getSubObjectId(xml) @@ -706,14 +700,11 @@ class ERP5Conduit(XMLSyncUtilsMixin): for subnode in self.getElementNodeList(xml): if not(subnode.nodeName in self.NOT_EDITABLE_PROPERTY): keyword_type = self.getPropertyType(subnode) - LOG('newObject',0,str(subnode.childNodes)) # This is the case where the property is a list keyword=str(subnode.nodeName) if len(subnode.childNodes) > 0: # We check that this tag is not empty data = subnode.childNodes[0].data args[keyword]=data - LOG('newObject',0,'keyword: %s' % str(keyword)) - LOG('newObject',0,'keywordtype: %s' % str(keyword_type)) #if args.has_key(keyword): # LOG('newObject',0,'data: %s' % str(args[keyword])) if args.has_key(keyword): @@ -722,8 +713,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): self.addNode(object=object,xml=subnode, force=1) # We should first edit the object args = self.getFormatedArgs(args=args) - LOG('newObject',0,"object.getphyspath: %s" % str(object.getPhysicalPath())) - LOG('newObject',0,"args: %s" % str(args)) # edit the object with a dictionnary of arguments, # like {"telephone_number":"02-5648"} #object._edit(**args) @@ -885,7 +874,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): dict_list = map(lambda x:x.split(':'),data[1:-1].split(',')) data = map(lambda (x,y):(x.replace(' ','').replace("'",''),int(y)),dict_list) data = dict(data) - LOG('convertXmlValue',0,'data: %s' % str(data)) return data # XXX is it the right place ? It should be in XupdateUtils, but here we @@ -997,7 +985,6 @@ class ERP5Conduit(XMLSyncUtilsMixin): add_action = self.isWorkflowActionAddable(object=object, status=status,wf_tool=wf_tool, wf_id=wf_id,xml=xml) - #LOG('addNode, workflow_history wf_conflict_list:',0,wf_conflict_list) LOG('addNode, workflow_history add_action:',0,add_action) if add_action and not simulate: LOG('addNode, setting status:',0,'ok') diff --git a/product/ERP5SyncML/Publication.py b/product/ERP5SyncML/Publication.py index 43a6b0bb94..4ba43575c9 100644 --- a/product/ERP5SyncML/Publication.py +++ b/product/ERP5SyncML/Publication.py @@ -152,7 +152,9 @@ class Publication(Subscription): constructors = (addPublication,) # Constructor - def __init__(self, id, title, publication_url, destination_path, query, xml_mapping, conduit, gpg_key): + def __init__(self, id, title, publication_url, destination_path, + query, xml_mapping, conduit, gpg_key, auth_required=False, + authentication_format='', authentication_type=''): """ constructor """ @@ -169,6 +171,9 @@ class Publication(Subscription): self.setConduit(conduit) Folder.__init__(self, id) self.title = title + self.auth_required = auth_required + self.authentication_format = authentication_format + self.authentication_type = authentication_type def getPublicationUrl(self): """ @@ -189,6 +194,42 @@ class Publication(Subscription): """ self.publication_url = publication_url + def isAuthenticationRequired(self): + """ + return False if authentication not required, True else + """ + return getattr(self, 'auth_required', False) + + def setAuthentication(self, auth): + """ + set the value of the authentication requirement + """ + self.auth_required = auth + + def getAuthenticationFormat(self): + """ + return the format of authentication + """ + return getattr(self, 'authentication_format', '') + + def getAuthenticationType(self): + """ + return the type of authentication + """ + return getattr(self, 'authentication_type', '') + + def setAuthenticationFormat(self, authentication_format): + """ + set the format of authentication + """ + self.authentication_format = authentication_format + + def setAuthenticationType(self, authentication_type): + """ + set the type of authentication + """ + self.authentication_type = authentication_type + def addSubscriber(self, subscriber): """ Add a new subscriber to the publication diff --git a/product/ERP5SyncML/PublicationSynchronization.py b/product/ERP5SyncML/PublicationSynchronization.py index 8859db0c8e..444d420031 100644 --- a/product/ERP5SyncML/PublicationSynchronization.py +++ b/product/ERP5SyncML/PublicationSynchronization.py @@ -34,6 +34,10 @@ from xml.dom.minidom import parse, parseString from XMLSyncUtils import XMLSyncUtils from Conduit.ERP5Conduit import ERP5Conduit from Products.CMFCore.utils import getToolByName +from Products.ERP5Security.ERP5UserManager import ERP5UserManager +from Products.PluggableAuthService.interfaces.plugins import\ + IAuthenticationPlugin +from AccessControl.SecurityManagement import newSecurityManager import commands from zLOG import LOG @@ -43,17 +47,19 @@ class PublicationSynchronization(XMLSyncUtils): """ def PubSyncInit(self, publication=None, xml_client=None, subscriber=None, - sync_type=None, auth_required=0): + sync_type=None): """ Read the client xml message Send the first XML message from the server """ LOG('PubSyncInit',0,'Starting... publication: %s' % str(publication)) - + #the session id is set at the same value of those of the client subscriber.setSessionId(self.getSessionId(xml_client)) - # for a new session, the message Id must be reset - subscriber.resetMessageId() + #same for the message id + subscriber.setMessageId(self.getMessageId(xml_client)) + #at the begining of the synchronization the subscriber is not authenticated + subscriber.setAuthenticated(False) #the last_message_id is 1 because the message that #we are about to send is the message 1 subscriber.initLastMessageId(1) @@ -67,33 +73,83 @@ class PublicationSynchronization(XMLSyncUtils): alert_code = self.getAlertCode(xml_client) cred = self.checkCred(xml_client) #XXX this is in developement, it's just for tests - if not cred and auth_required: - LOG('PubSyncInit',0,'authentication required') - # Prepare the xml message for the Sync initialization package - cmd_id = 1 # specifies a SyncML message-unique command identifier - xml_list = [] - xml = xml_list.append - xml('<SyncML>\n') - # syncml header - xml(self.SyncMLHeader(subscriber.getSessionId(), - subscriber.incrementMessageId(), subscriber.getSubscriptionUrl(), - publication.getPublicationUrl())) - # syncml body - xml(' <SyncBody>\n') - # chal message - xml(self.SyncMLChal(cmd_id, "SyncHdr", publication.getPublicationUrl(), - subscriber.getSubscriptionUrl(), "b64", "syncml:auth-basic", - self.UNAUTHORIZED)) - cmd_id += 1 - - xml(' </SyncBody>\n') - - xml('</SyncML>\n') - xml_a = ''.join(xml_list) - - self.sendResponse(from_url=publication.getPublicationUrl(), - to_url=subscriber.getSubscriptionUrl(),sync_id=publication.getTitle(), - xml=xml_a,domain=publication) + if publication.isAuthenticationRequired(): + if not cred: + LOG('PubSyncInit',0,'authentication required') + # Prepare the xml message for the Sync initialization package + cmd_id = 1 # specifies a SyncML message-unique command identifier + xml_list = [] + xml = xml_list.append + xml('<SyncML>\n') + # syncml header + xml(self.SyncMLHeader(subscriber.getSessionId(), + subscriber.getMessageId(), subscriber.getSubscriptionUrl(), + publication.getPublicationUrl())) + # syncml body + xml(' <SyncBody>\n') + # chal message + xml(self.SyncMLChal(cmd_id, "SyncHdr", + publication.getPublicationUrl(), subscriber.getSubscriptionUrl(), + publication.getAuthenticationFormat(), + publication.getAuthenticationType(), self.AUTH_REQUIRED)) + cmd_id += 1 + + xml(' </SyncBody>\n') + + xml('</SyncML>\n') + xml_a = ''.join(xml_list) + + self.sendResponse(from_url=publication.getPublicationUrl(), + to_url=subscriber.getSubscriptionUrl(), + sync_id=publication.getTitle(), xml=xml_a, domain=publication) + + else:#(if the subscriber begin the session with a cred) -> to be tested + (authentication_format, authentication_type, data) = self.getCred(xml_client) + #at the begining, the code is initialised at UNAUTHORIZED + auth_code=self.UNAUTHORIZED + + if authentication_format == publication.getAuthenticationFormat(): + if authentication_type == publication.getAuthenticationType(): + decoded = subscriber.decode(authentication_format, data) + if decoded not in ('', None) and decoded.__contains__(':'): + (login, password) = decoded.split(':') + uf = self.getPortalObject().acl_users + for plugin_name, plugin in uf._getOb('plugins').listPlugins( + IAuthenticationPlugin ): + if plugin.authenticateCredentials( + {'login':login, 'password':password}) is not None: + subscriber.setAuthenticated(True) + auth_code=self.AUTH_ACCEPTED + #here we must log in with the user authenticated : + user = uf.getUserById(login).__of__(uf) + newSecurityManager(None, user) + subscriber.setUser(login) + break + #in all others cases, the auth_code is set to UNAUTHORIZED + + + # Prepare the xml message for the Sync initialization package + cmd_id = 1 # specifies a SyncML message-unique command identifier + xml_list = [] + xml = xml_list.append + xml('<SyncML>\n') + # syncml header + xml(self.SyncMLHeader(subscriber.getSessionId(), + subscriber.getMessageId(), + subscriber.getSubscriptionUrl(), + publication.getPublicationUrl())) + # syncml body + xml(' <SyncBody>\n') + xml(self.SyncMLStatus(cmd_id, subscriber.getSubscriptionUrl(), + publication.getPublicationUrl(), auth_code)) + xml(' </SyncBody>\n') + xml('</SyncML>\n') + xml_a = ''.join(xml_list) + + self.sendResponse(from_url=publication.getPublicationUrl(), + to_url=subscriber.getSubscriptionUrl(), + sync_id=publication.getTitle(), xml=xml_a, domain=publication) + else : # If slow sync, then resend everything if alert_code == self.SLOW_SYNC: @@ -103,8 +159,7 @@ class PublicationSynchronization(XMLSyncUtils): # Check if the last time synchronization is the same as the client one mess='\nsubscriber.getNextAnchor:\t%s\nsubscriber.getLastAnchor:\t%s\ - \nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % (subscriber.getNextAnchor(), - subscriber.getLastAnchor(), last_anchor, next_anchor) + \nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % (subscriber.getNextAnchor(), subscriber.getLastAnchor(), last_anchor, next_anchor) LOG('PubSyncInit',0,mess) if subscriber.getNextAnchor() != last_anchor: @@ -112,8 +167,8 @@ class PublicationSynchronization(XMLSyncUtils): LOG('PubSyncInit',0,'anchor null') raise ValueError, "Sorry, the anchor was null" else: - message = "bad anchors in PubSyncInit! " + subscriber.getNextAnchor() + \ - " and " + last_anchor + message = "bad anchors in PubSyncInit! " + \ + subscriber.getNextAnchor() + " and " + last_anchor LOG('PubSyncInit',0,message) else: subscriber.setNextAnchor(next_anchor) @@ -123,8 +178,7 @@ class PublicationSynchronization(XMLSyncUtils): # We have started the sync from the server (may be for a conflict resolution) pass - if alert is not None and auth_required==0: - #if 1: + if alert is not None and not publication.isAuthenticationRequired(): # Prepare the xml message for the Sync initialization package cmd_id = 1 # specifies a SyncML message-unique command identifier xml_list = [] @@ -203,7 +257,15 @@ class PublicationSynchronization(XMLSyncUtils): result = self.PubSyncInit(publication=publication, xml_client=xml_client, subscriber=subscriber, sync_type=alert_code) else: - result = self.PubSyncModif(publication, xml_client) + #we log the user authenticated to do the synchronization with him + if publication.isAuthenticationRequired(): + if subscriber.isAuthenticated(): + uf = self.getPortalObject().acl_users + user = uf.getUserById(subscriber.getUser()).__of__(uf) + newSecurityManager(None, user) + result = self.PubSyncModif(publication, xml_client) + else: + result = self.PubSyncModif(publication, xml_client) elif subscriber is not None: # This looks like we are starting a synchronization after # a conflict resolution by the user diff --git a/product/ERP5SyncML/Subscription.py b/product/ERP5SyncML/Subscription.py index dcc09c90d0..e920c5e49e 100644 --- a/product/ERP5SyncML/Subscription.py +++ b/product/ERP5SyncML/Subscription.py @@ -41,6 +41,11 @@ from zLOG import LOG import md5 +try: + from base64 import b64encode, b64decode +except ImportError: + from base64 import encodestring as b64encode, decodestring as b64decode + #class Conflict(SyncCode, Implicit): class Conflict(SyncCode, Base): """ @@ -53,8 +58,8 @@ class Conflict(SyncCode, Base): isIndexable = 0 isPortalContent = 0 # Make sure RAD generated accessors at the class level - def __init__(self, object_path=None, keyword=None, xupdate=None, publisher_value=None,\ - subscriber_value=None, subscriber=None): + def __init__(self, object_path=None, keyword=None, xupdate=None, + publisher_value=None, subscriber_value=None, subscriber=None): self.object_path=object_path self.keyword = keyword self.setLocalValue(publisher_value) @@ -614,8 +619,8 @@ class Subscription(Folder, SyncCode): isPortalContent = 1 isRADContent = 1 icon = None - isIndexable = 0 + user = None # Declarative properties property_sheets = ( PropertySheet.Base @@ -635,7 +640,8 @@ class Subscription(Folder, SyncCode): ) # Constructor - def __init__(self, id, title, publication_url, subscription_url, destination_path, query, xml_mapping, conduit, gpg_key): + def __init__(self, id, title, publication_url, subscription_url, destination_path, query, xml_mapping, conduit, gpg_key, login, password, + authentication_format='', authentication_type=''): """ We need to create a dictionnary of signatures of documents which belong to the synchronisation @@ -652,6 +658,10 @@ class Subscription(Folder, SyncCode): #self.signatures = PersistentMapping() self.last_anchor = '00000000T000000Z' self.next_anchor = '00000000T000000Z' + self.login=login + self.password=password + self.authentication_format=authentication_format + self.authentication_type=authentication_type self.domain_type = self.SUB self.gpg_key = gpg_key self.setGidGenerator(None) @@ -714,28 +724,28 @@ class Subscription(Folder, SyncCode): We will see if the last session id was the same wich means that the same message was sent again - return 1 if the session id was not seen, 0 if already seen + return True if the session id was not seen, False if already seen """ last_session_id = getattr(self,'last_session_id',None) if last_session_id == session_id: - return 0 + return False self.last_session_id = session_id - return 1 + return True def checkCorrectRemoteMessageId(self, message_id): """ We will see if the last message id was the same wich means that the same message was sent again - return 1 if the message id was not seen, 0 if already seen + return True if the message id was not seen, False if already seen """ last_message_id = getattr(self,'last_message_id',None) # LOG('checkCorrectRemoteMessageId last_message_id =',0,last_message_id) # LOG('checkCorrectRemoteMessageId message_id =',0,message_id) if last_message_id == message_id: - return 0 + return False self.last_message_id = message_id - return 1 + return True def initLastMessageId(self, last_message_id=None): """ @@ -872,25 +882,74 @@ class Subscription(Folder, SyncCode): """ return self.gid_generator + def getLogin(self): + """ + This method return the login of this subscription + """ + return getattr(self, 'login', '') + + def setLogin(self, new_login): + """ + set the login at new_login + """ + self.login=new_login + + def getPassword(self): + """ + This method return the password of this subscription + """ + return getattr(self, 'password', '') + + def setPassword(self, new_password): + """ + set the password at new_password + """ + self.password=new_password + + + def setAuthentication(self, auth): + """ + set the value of the authentication requirement + """ + self.auth_required = auth + + def getAuthenticationFormat(self): + """ + return the format of authentication + """ + return getattr(self, 'authentication_format', '') + + def getAuthenticationType(self): + """ + return the type of authentication + """ + return getattr(self, 'authentication_type', '') + + def setAuthenticationFormat(self, authentication_format): + """ + set the format of authentication + """ + self.authentication_format = authentication_format + + def setAuthenticationType(self, authentication_type): + """ + set the type of authentication + """ + self.authentication_type = authentication_type + def getGidFromObject(self, object): """ """ o_base = aq_base(object) o_gid = None - # LOG('getGidFromObject',0,'gidgenerator : _%s_' % repr(self.getGidGenerator())) gid_gen = self.getGidGenerator() if callable(gid_gen): - # LOG('getGidFromObject gid_generator',0,'is callable') o_gid=gid_gen(object) - # LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid)) elif getattr(o_base, gid_gen, None) is not None: - # LOG('getGidFromObject',0,'there is the gid generator on o_base') generator = getattr(object, gid_gen) o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant - # LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid)) elif gid_gen is not None: # It might be a script python - # LOG('getGidFromObject',0,'there is the gid generator') generator = getattr(object,gid_gen) o_gid = generator() # XXX - used to be o_gid = generator(object=object) which is redundant # LOG('getGidFromObject',0,'o_gid: %s' % repr(o_gid)) @@ -911,7 +970,6 @@ class Subscription(Folder, SyncCode): # LOG('getObjectFromGid oject_list',0,object_list) if signature is not None and signature.getId() is not None: o_id = signature.getId() - # LOG('getObjectFromGid o_id',0,o_id) o = None try: o = destination._getOb(o_id) @@ -920,7 +978,6 @@ class Subscription(Folder, SyncCode): if o is not None and o in object_list: return o for o in object_list: - # LOG('getObjectFromGid',0,'working on : %s' % repr(o)) o_gid = self.getGidFromObject(o) if o_gid == gid: return o @@ -1073,6 +1130,12 @@ class Subscription(Folder, SyncCode): set the message id to 0 """ self.message_id = 0 + + def setMessageId(self, message_id): + """ + set the message id to message_id + """ + self.message_id = message_id def getLastAnchor(self): """ @@ -1230,3 +1293,67 @@ class Subscription(Folder, SyncCode): o.setTempXML(None) self.setRemainingObjectPathList(None) + + def isAuthenticated(self): + """ + return True if the subscriber is authenticated for this session, False + in other case + """ + return self.is_authenticated + + def setAuthenticated(self, value): + """ + set at True or False the value of is_authenticated is the subscriber + is authenticated for this session or not + """ + self.is_authenticated = value + + def encode(self, format, string_to_encode): + """ + return the string_to_encode encoded with format format + """ + if format in ('', None): + return string_to_encode + if format == 'b64': + return b64encode(string_to_encode) + #elif format is .... put here the other formats + else:#if there is no format corresponding with format, raise an error + LOG('encode : unknown or not implemented format :', 0, format) + raise ValueError, "Sorry, the format %s is unknow or not implemented" % format + + def decode(self, format, string_to_decode): + """ + return the string_to_decode decoded with format format + """ + string_to_decode = string_to_decode.encode('utf-8') + if format in ('', None): + return string_to_decode + if format == 'b64': + return b64decode(string_to_decode) + #elif format is .... put here the other formats + else:#if there is no format corresponding with format, raise an error + LOG('decode : unknown or not implemented format :', 0, format) + raise ValueError, "Sorry, the format %s is unknow or not implemented" % format + + def isDecodeEncodeTheSame(self, string_encoded, string_decoded, format): + """ + return True if the string_encoded is equal to string_decoded encoded + in format + """ + isTheSame=False + if self.encode(format, string_decoded) == string_encoded: + isTheSame=True + return isTheSame + + + def setUser(self, user): + """ + save the user logged in to log him on each transaction + """ + self.user=user + + def getUser(self): + """ + retrun the user logged in + """ + return self.user diff --git a/product/ERP5SyncML/SubscriptionSynchronization.py b/product/ERP5SyncML/SubscriptionSynchronization.py index d59581252a..ae05fc9c99 100644 --- a/product/ERP5SyncML/SubscriptionSynchronization.py +++ b/product/ERP5SyncML/SubscriptionSynchronization.py @@ -44,6 +44,8 @@ class SubscriptionSynchronization(XMLSyncUtils): """ LOG('SubSyncInit',0,'starting....') cmd_id = 1 # specifies a SyncML message-unique command identifier + subscription.NewAnchor() + subscription.initLastMessageId() xml_list = [] xml = xml_list.append xml('<SyncML>\n') @@ -54,8 +56,6 @@ class SubscriptionSynchronization(XMLSyncUtils): # syncml body xml(' <SyncBody>\n') - subscription.NewAnchor() - subscription.initLastMessageId() # We have to set every object as NOT_SYNCHRONIZED subscription.startSynchronization() @@ -100,7 +100,21 @@ class SubscriptionSynchronization(XMLSyncUtils): xml_client = msg if isinstance(xml_client, str) or isinstance(xml_client, unicode): xml_client = parseString(xml_client) - response = self.SubSyncModif(self.getSubscription(id),xml_client) + next_status = self.getNextSyncBodyStatus(xml_client, None) + #LOG('readResponse, next status :',0,next_status) + if next_status is not None: + status_code = self.getStatusCode(next_status) + #LOG('readResponse status code :',0,status_code) + if status_code == self.AUTH_REQUIRED: + #LOG('readResponse', 0, 'Authentication required') + response = self.SubSyncCred(id, xml_client) + elif status_code == self.UNAUTHORIZED: + #LOG('readResponse', 0, 'Bad authentication') + return {'has_response':0,'xml':xml_client} + else: + response = self.SubSyncModif(self.getSubscription(id), xml_client) + else: + response = self.SubSyncModif(self.getSubscription(id), xml_client) if RESPONSE is not None: @@ -108,6 +122,53 @@ class SubscriptionSynchronization(XMLSyncUtils): else: return response + def SubSyncCred (self, id, msg=None, RESPONSE=None): + """ + This method send crendentials + """ + + LOG('SubSyncCred',0,'starting... id: %s' % str(id)) + LOG('SubSyncCred',0,'starting... msg: %s' % str(msg)) + + cmd_id = 1 # specifies a SyncML message-unique command identifier + subscription = self.getSubscription(id) + xml_list = [] + xml = xml_list.append + xml('<SyncML>\n') + # syncml header + data = "%s:%s" % (subscription.getLogin(), subscription.getPassword()) + data=subscription.encode(subscription.getAuthenticationFormat(), data) + xml(self.SyncMLHeader(subscription.getSessionId(), + subscription.incrementMessageId(), subscription.getPublicationUrl(), + subscription.getSubscriptionUrl(), dataCred=data, + authentication_format=subscription.getAuthenticationFormat(), + authentication_type=subscription.getAuthenticationType())) + + # syncml body + xml(' <SyncBody>\n') + + # alert message + xml(self.SyncMLAlert(cmd_id, subscription.getSynchronizationType(), + subscription.getPublicationUrl(), + subscription.getDestinationPath(), + subscription.getLastAnchor(), + subscription.getNextAnchor())) + cmd_id += 1 + + xml(' <Put>\n') + xml(' <CmdID>%s</CmdID>\n' % cmd_id) + cmd_id += 1 + xml(' </Put>\n') + xml(' </SyncBody>\n') + xml('</SyncML>\n') + xml_a = ''.join(xml_list) + + self.sendResponse(from_url=subscription.subscription_url, + to_url=subscription.publication_url, sync_id=subscription.getTitle(), + xml=xml_a,domain=subscription) + + return {'has_response':1,'xml':xml_a} + def SubSyncModif(self, subscription, xml_client): """ Send the client modification, this happens after the Synchronization diff --git a/product/ERP5SyncML/SynchronizationTool.py b/product/ERP5SyncML/SynchronizationTool.py index e7120f841f..6bb230a57d 100644 --- a/product/ERP5SyncML/SynchronizationTool.py +++ b/product/ERP5SyncML/SynchronizationTool.py @@ -62,8 +62,8 @@ from zLOG import LOG -class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronization, - UniqueObject, Folder): +class SynchronizationTool( SubscriptionSynchronization, + PublicationSynchronization, UniqueObject, Folder): """ This tool implements the synchronization algorithm @@ -159,9 +159,11 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati + '?manage_tabs_message=Tool+updated.' ) - security.declareProtected(Permissions.ModifyPortalContent, 'manage_addPublication') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_addPublication') def manage_addPublication(self, title, publication_url, destination_path, - query, xml_mapping, conduit, gpg_key, RESPONSE=None): + query, xml_mapping, conduit, gpg_key, auth_required=0, + authentication_format='', authentication_type='', RESPONSE=None): """ create a new publication """ @@ -171,7 +173,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati folder = self.getObjectContainer() new_id = self.getPublicationIdFromTitle(title) pub = Publication(new_id, title, publication_url, destination_path, - query, xml_mapping, conduit, gpg_key) + query, xml_mapping, conduit, gpg_key, auth_required, + authentication_format, authentication_type) folder._setObject( new_id, pub ) #if len(self.list_publications) == 0: # self.list_publications = PersistentMapping() @@ -179,9 +182,12 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('managePublications') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_addSubscription') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_addSubscription') def manage_addSubscription(self, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key, RESPONSE=None): + destination_path, query, xml_mapping, conduit, gpg_key, + login=None, password=None, authentication_format='', + authentication_type='',RESPONSE=None): """ XXX should be renamed as addSubscription create a new subscription @@ -192,7 +198,9 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati folder = self.getObjectContainer() new_id = self.getSubscriptionIdFromTitle(title) sub = Subscription(new_id, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key) + destination_path, query, xml_mapping, conduit, gpg_key, + login, password, authentication_format, + authentication_type) folder._setObject( new_id, sub ) #if len(self.list_subscriptions) == 0: # self.list_subscriptions = PersistentMapping() @@ -200,10 +208,12 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('manageSubscriptions') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_editPublication') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_editPublication') def manage_editPublication(self, title, publication_url, destination_path, query, xml_mapping, conduit, gpg_key, id_generator, - gid_generator, RESPONSE=None): + gid_generator, auth_required=0, authentication_format='', + authentication_type='', RESPONSE=None): """ modify a publication """ @@ -217,13 +227,19 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati pub.setGPGKey(gpg_key) pub.setIdGenerator(id_generator) pub.setGidGenerator(gid_generator) + pub.setAuthentication(auth_required) + pub.setAuthenticationFormat(authentication_format) + pub.setAuthenticationType(authentication_type) + if RESPONSE is not None: RESPONSE.redirect('managePublications') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_editSubscription') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_editSubscription') def manage_editSubscription(self, title, publication_url, subscription_url, - destination_path, query, xml_mapping, conduit, gpg_key, id_generator, - gid_generator, RESPONSE=None): + destination_path, query, xml_mapping, conduit, gpg_key, id_generator, + gid_generator,login='', password='', authentication_format='', + authentication_type='', RESPONSE=None): """ modify a subscription """ @@ -238,10 +254,15 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati sub.setSubscriptionUrl(subscription_url) sub.setIdGenerator(id_generator) sub.setGidGenerator(gid_generator) + sub.setLogin(login) + sub.setPassword(password) + sub.setAuthenticationFormat(authentication_format) + sub.setAuthenticationType(authentication_type) if RESPONSE is not None: RESPONSE.redirect('manageSubscriptions') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_deletePublication') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_deletePublication') def manage_deletePublication(self, title, RESPONSE=None): """ delete a publication @@ -252,7 +273,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('managePublications') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_deleteSubscription') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_deleteSubscription') def manage_deleteSubscription(self, title, RESPONSE=None): """ delete a subscription @@ -263,7 +285,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('manageSubscriptions') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_resetPublication') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_resetPublication') def manage_resetPublication(self, title, RESPONSE=None): """ reset a publication @@ -273,7 +296,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('managePublications') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_resetSubscription') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_resetSubscription') def manage_resetSubscription(self, title, RESPONSE=None): """ reset a subscription @@ -284,7 +308,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('manageSubscriptions') - security.declareProtected(Permissions.ModifyPortalContent, 'manage_syncSubscription') + security.declareProtected(Permissions.ModifyPortalContent, + 'manage_syncSubscription') def manage_syncSubscription(self, title, RESPONSE=None): """ reset a subscription @@ -293,7 +318,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('manageSubscriptions') - security.declareProtected(Permissions.AccessContentsInformation,'getPublicationList') + security.declareProtected(Permissions.AccessContentsInformation, + 'getPublicationList') def getPublicationList(self): """ Return a list of publications @@ -303,7 +329,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati object_list = filter(lambda x: x.id.find('pub')==0,object_list) return object_list - security.declareProtected(Permissions.AccessContentsInformation,'getPublication') + security.declareProtected(Permissions.AccessContentsInformation, + 'getPublication') def getPublication(self, title): """ Return the publications with this id @@ -313,7 +340,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati return p return None - security.declareProtected(Permissions.AccessContentsInformation,'getObjectContainer') + security.declareProtected(Permissions.AccessContentsInformation, + 'getObjectContainer') def getObjectContainer(self): """ this returns the external mount point if there is one @@ -325,7 +353,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati folder = root.external_mount_point return folder - security.declareProtected(Permissions.AccessContentsInformation,'getSubscriptionList') + security.declareProtected(Permissions.AccessContentsInformation, + 'getSubscriptionList') def getSubscriptionList(self): """ Return a list of publications @@ -345,7 +374,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati return None - security.declareProtected(Permissions.AccessContentsInformation,'getSynchronizationList') + security.declareProtected(Permissions.AccessContentsInformation, + 'getSynchronizationList') def getSynchronizationList(self): """ Returns the list of subscriptions and publications @@ -353,7 +383,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati """ return self.getSubscriptionList() + self.getPublicationList() - security.declareProtected(Permissions.AccessContentsInformation,'getSubscriberList') + security.declareProtected(Permissions.AccessContentsInformation, + 'getSubscriberList') def getSubscriberList(self): """ Returns the list of subscribers and subscriptions @@ -364,13 +395,15 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati s_list += publication.getSubscriberList() return s_list - security.declareProtected(Permissions.AccessContentsInformation,'getConflictList') + security.declareProtected(Permissions.AccessContentsInformation, + 'getConflictList') def getConflictList(self, context=None): """ Retrieve the list of all conflicts Here the list is as follow : [conflict_1,conflict2,...] where conflict_1 is like: - ['publication',publication_id,object.getPath(),property_id,publisher_value,subscriber_value] + ['publication',publication_id,object.getPath(),property_id, + publisher_value,subscriber_value] """ path = self.resolveContext(context) conflict_list = [] @@ -385,7 +418,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati conflict_list += [conflict.__of__(subscriber)] for subscription in self.getSubscriptionList(): sub_conflict_list = subscription.getConflictList() - LOG('SynchronizationTool.getConflictList, sub_conflict_list',0,sub_conflict_list) + LOG('SynchronizationTool.getConflictList, sub_conflict_list',0, + sub_conflict_list) for conflict in sub_conflict_list: if isinstance(conflict,str): import pdb; pdb.set_trace() @@ -402,7 +436,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati # return new_list return conflict_list - security.declareProtected(Permissions.AccessContentsInformation,'getDocumentConflictList') + security.declareProtected(Permissions.AccessContentsInformation, + 'getDocumentConflictList') def getDocumentConflictList(self, context=None): """ Retrieve the list of all conflicts for a given document @@ -411,7 +446,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati return self.getConflictList(context) - security.declareProtected(Permissions.AccessContentsInformation,'getSynchronizationState') + security.declareProtected(Permissions.AccessContentsInformation, + 'getSynchronizationState') def getSynchronizationState(self, context): """ context : the context on which we are looking for state @@ -464,7 +500,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati state_list += [[subscriber,state]] return state_list - security.declareProtected(Permissions.ModifyPortalContent, 'applyPublisherValue') + security.declareProtected(Permissions.ModifyPortalContent, + 'applyPublisherValue') def applyPublisherValue(self, conflict): """ after a conflict resolution, we have decided @@ -494,7 +531,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati directory._delObject(copy_id) signature.setStatus(self.PUB_CONFLICT_MERGE) - security.declareProtected(Permissions.ModifyPortalContent, 'applyPublisherDocument') + security.declareProtected(Permissions.ModifyPortalContent, + 'applyPublisherDocument') def applyPublisherDocument(self, conflict): """ apply the publisher value for all conflict of the given document @@ -506,7 +544,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati LOG('applyPublisherDocument, applying on conflict: ',0,conflict) c.applyPublisherValue() - security.declareProtected(Permissions.AccessContentsInformation, 'getPublisherDocumentPath') + security.declareProtected(Permissions.AccessContentsInformation, + 'getPublisherDocumentPath') def getPublisherDocumentPath(self, conflict): """ apply the publisher value for all conflict of the given document @@ -514,7 +553,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati subscriber = conflict.getSubscriber() return conflict.getObjectPath() - security.declareProtected(Permissions.AccessContentsInformation, 'getPublisherDocument') + security.declareProtected(Permissions.AccessContentsInformation, + 'getPublisherDocument') def getPublisherDocument(self, conflict): """ apply the publisher value for all conflict of the given document @@ -548,8 +588,9 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati directory._delObject(object_id) # Import the conduit and get it conduit_name = subscriber.getConduit() - conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), globals(), locals(), ['']) - conduit = getattr(conduit_module, conduit_name)() + conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), + globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() conduit.addNode(xml=publisher_xml,object=directory,object_id=object_id) subscriber_document = directory._getOb(object_id) for c in self.getConflictList(conflict.getObjectPath()): @@ -570,24 +611,33 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati object_id = repotool._getId(docid, new_rev) return object_id - security.declareProtected(Permissions.AccessContentsInformation, 'getSubscriberDocumentPath') + security.declareProtected(Permissions.AccessContentsInformation, + 'getSubscriberDocumentPath') def getSubscriberDocumentPath(self, conflict): """ apply the publisher value for all conflict of the given document """ copy_path = conflict.getCopyPath() if copy_path is not None: - return copy_path + return copy_path subscriber = conflict.getSubscriber() publisher_object_path = conflict.getObjectPath() publisher_object = self.unrestrictedTraverse(publisher_object_path) - publisher_xml = self.getXMLObject(object=publisher_object,xml_mapping = subscriber.getXMLMapping()) + publisher_xml = self.getXMLObject(object=publisher_object, + xml_mapping = subscriber.getXMLMapping()) directory = publisher_object.aq_inner.aq_parent object_id = self._getCopyId(publisher_object) # Import the conduit and get it conduit_name = subscriber.getConduit() - conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), globals(), locals(), ['']) - conduit = getattr(conduit_module, conduit_name)() + if conduit_name.startswith('Products'): + path = conduit_name + conduit_name = conduit_name.split('.')[-1] + conduit_module = __import__(path, globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() + else: + conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), + globals(), locals(), ['']) + conduit = getattr(conduit_module, conduit_name)() conduit.addNode(xml=publisher_xml,object=directory,object_id=object_id) subscriber_document = directory._getOb(object_id) subscriber_document._conflict_resolution = 1 @@ -598,7 +648,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati conflict.setCopyPath(copy_path) return copy_path - security.declareProtected(Permissions.AccessContentsInformation, 'getSubscriberDocument') + security.declareProtected(Permissions.AccessContentsInformation, + 'getSubscriberDocument') def getSubscriberDocument(self, conflict): """ apply the publisher value for all conflict of the given document @@ -607,7 +658,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati subscriber_object = self.unrestrictedTraverse(subscriber_object_path) return subscriber_object - security.declareProtected(Permissions.ModifyPortalContent, 'applySubscriberDocument') + security.declareProtected(Permissions.ModifyPortalContent, + 'applySubscriberDocument') def applySubscriberDocument(self, conflict): """ apply the subscriber value for all conflict of the given document @@ -617,7 +669,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if c.getSubscriber() == subscriber: c.applySubscriberValue() - security.declareProtected(Permissions.ModifyPortalContent, 'applySubscriberValue') + security.declareProtected(Permissions.ModifyPortalContent, + 'applySubscriberValue') def applySubscriberValue(self, conflict,object=None): """ after a conflict resolution, we have decided @@ -636,7 +689,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati signature = subscriber.getSignature(object.getId()) # XXX may be change for rid # Import the conduit and get it conduit_name = subscriber.getConduit() - conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), globals(), locals(), ['']) + conduit_module = __import__('.'.join([Conduit.__name__, conduit_name]), + globals(), locals(), ['']) conduit = getattr(conduit_module, conduit_name)() for xupdate in conflict.getXupdateList(): conduit.updateNode(xml=xupdate,object=object,force=1) @@ -656,15 +710,18 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati directory._delObject(copy_id) signature.setStatus(self.PUB_CONFLICT_MERGE) - security.declareProtected(Permissions.ModifyPortalContent, 'managePublisherValue') - def managePublisherValue(self, subscription_url, property_id, object_path, RESPONSE=None): + security.declareProtected(Permissions.ModifyPortalContent, + 'managePublisherValue') + def managePublisherValue(self, subscription_url, property_id, object_path, + RESPONSE=None): """ Do whatever needed in order to store the local value on the remote server Suggestion (API) add method to view document with applied xupdate - of a given subscriber XX (ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd) + of a given subscriber XX + (ex. viewSubscriberDocument?path=ddd&subscriber_id=dddd) Version=Version CPS """ # Retrieve the conflict object @@ -681,8 +738,10 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('manageConflicts') - security.declareProtected(Permissions.ModifyPortalContent, 'manageSubscriberValue') - def manageSubscriberValue(self, subscription_url, property_id, object_path, RESPONSE=None): + security.declareProtected(Permissions.ModifyPortalContent, + 'manageSubscriberValue') + def manageSubscriberValue(self, subscription_url, property_id, object_path, + RESPONSE=None): """ Do whatever needed in order to store the remote value locally and confirmed that the remote box should keep it's value @@ -700,7 +759,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati if RESPONSE is not None: RESPONSE.redirect('manageConflicts') - security.declareProtected(Permissions.ModifyPortalContent, 'manageSubscriberDocument') + security.declareProtected(Permissions.ModifyPortalContent, + 'manageSubscriberDocument') def manageSubscriberDocument(self, subscription_url, object_path): """ """ @@ -711,7 +771,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati break self.managePublisherDocument(object_path) - security.declareProtected(Permissions.ModifyPortalContent, 'managePublisherDocument') + security.declareProtected(Permissions.ModifyPortalContent, + 'managePublisherDocument') def managePublisherDocument(self, object_path): """ """ @@ -742,7 +803,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati return context.getPhysicalPath() security.declarePublic('sendResponse') - def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, domain=None, send=1): + def sendResponse(self, to_url=None, from_url=None, sync_id=None,xml=None, + domain=None, send=1): """ We will look at the url and we will see if we need to send mail, http response, or just copy to a file. @@ -760,7 +822,9 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati decrypted.write(xml) decrypted.close() (status,output)=commands.getstatusoutput('gzip /tmp/%s' % filename) - (status,output)=commands.getstatusoutput('gpg --yes --homedir /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se /tmp/%s.gz' % (gpg_key,filename)) + (status,output)=commands.getstatusoutput('gpg --yes --homedir \ + /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" -se \ + /tmp/%s.gz' % (gpg_key,filename)) LOG('readResponse, gpg output:',0,output) encrypted = file('/tmp/%s.gz.gpg' % filename,'r') xml = encrypted.read() @@ -775,7 +839,7 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati return None # we will send an http response domain = aq_base(domain) - LOG('sendResponse, will start sendHttpResponse, xml\n',0,xml) + LOG('sendResponse, will start sendHttpResponse, xml',0,'') self.activate(activity='RAMQueue').sendHttpResponse(sync_id=sync_id, to_url=to_url, xml=xml, domain=domain) @@ -814,7 +878,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati pass_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() auth_handler = urllib2.HTTPBasicAuthHandler(pass_mgr) proxy_auth_handler = urllib2.ProxyBasicAuthHandler(pass_mgr) - opener = urllib2.build_opener(proxy_handler, proxy_auth_handler,auth_handler,urllib2.HTTPHandler) + opener = urllib2.build_opener(proxy_handler, proxy_auth_handler, + auth_handler, urllib2.HTTPHandler) urllib2.install_opener(opener) to_encode = {'text':xml,'sync_id':sync_id} encoded = urllib.urlencode(to_encode) @@ -825,7 +890,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati try: result = urllib2.urlopen(request).read() except socket.error, msg: - self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url,sync_id=sync_id,xml=xml,domain=domain) + self.activate(activity='RAMQueue').sendHttpResponse(to_url=to_url, + sync_id=sync_id, xml=xml, domain=domain) LOG('sendHttpResponse, socket ERROR:',0,msg) return @@ -853,7 +919,6 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati # Login as a manager to make sure we can create objects uf = self.acl_users user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'') - #user = uf.getUserById('syncml').__of__(uf) newSecurityManager(None, user) message_list = self.portal_activities.getMessageList() LOG('sync, message_list:',0,message_list) @@ -871,12 +936,12 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati LOG('readResponse, ',0,'starting') LOG('readResponse, self.getPhysicalPath: ',0,self.getPhysicalPath()) LOG('readResponse, sync_id: ',0,sync_id) - #LOG('readResponse, text:',0,text) # Login as a manager to make sure we can create objects uf = self.acl_users user = uf.getUserById('syncml').__of__(uf) user = UnrestrictedUser('syncml','syncml',['Manager','Member'],'') newSecurityManager(None, user) + status_code = None if text is not None: # XXX We will look everywhere for a publication/subsription with @@ -896,46 +961,38 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati encrypted = file('/tmp/%s.gz.gpg' % filename,'w') encrypted.write(text) encrypted.close() - (status,output)=commands.getstatusoutput('gpg --homedir /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" --decrypt /tmp/%s.gz.gpg > /tmp/%s.gz' % (gpg_key,filename,filename)) - LOG('readResponse, gpg output:',0,output) + (status,output)=commands.getstatusoutput('gpg --homedir \ + /var/lib/zope/Products/ERP5SyncML/gnupg_keys -r "%s" --decrypt \ + /tmp/%s.gz.gpg > /tmp/%s.gz' % (gpg_key, filename, filename)) + LOG('readResponse, gpg output:', 0, output) (status,output)=commands.getstatusoutput('gunzip /tmp/%s.gz' % filename) decrypted = file('/tmp/%s' % filename,'r') text = decrypted.read() - LOG('readResponse, text:',0,text) + LOG('readResponse, text:', 0, text) decrypted.close() commands.getstatusoutput('rm -f /tmp/%s' % filename) commands.getstatusoutput('rm -f /tmp/%s.gz.gpg' % filename) # Get the target and then find the corresponding publication or # Subscription - LOG('readResponse, xml before parseSTring\n',0,text) xml = parseString(text) - #XXX this function is not very optimized and should be improved url = self.getTarget(xml) - for publication in self.getPublicationList(): - if publication.getPublicationUrl()==url and publication.getTitle()==sync_id: + if publication.getPublicationUrl()==url and \ + publication.getTitle()==sync_id: result = self.PubSync(sync_id,xml) # Then encrypt the message xml = result['xml'] - #must be commented because this method is alredy called #xml = self.sendResponse(xml=xml,domain=publication,send=0) return xml + for subscription in self.getSubscriptionList(): if subscription.getSubscriptionUrl()==url and \ subscription.getTitle()==sync_id: - next_status = self.getNextSyncBodyStatus(xml, None) - if next_status is not None: - status_code = self.getStatusCode(next_status) - LOG('readResponse status code :',0,status_code) - if status_code == self.UNAUTHORIZED or \ - status_code == self.AUTH_REQUIRED: - LOG('readResponse', 0, 'Authentication required') - raise ValueError, "Authentication required" - else: - result = self.activate(activity='RAMQueue').SubSync(sync_id,xml) - #result = self.SubSync(sync_id,xml) + result = self.activate(activity='RAMQueue').SubSync(sync_id, + text) + #result = self.SubSync(sync_id,xml) # we use from only if we have a file elif isinstance(from_url, str): @@ -953,14 +1010,16 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati xml = None return xml - security.declareProtected(Permissions.ModifyPortalContent, 'getPublicationIdFromTitle') + security.declareProtected(Permissions.ModifyPortalContent, + 'getPublicationIdFromTitle') def getPublicationIdFromTitle(self, title): """ simply return an id from a title """ return 'pub_' + title - security.declareProtected(Permissions.ModifyPortalContent, 'getPublicationIdFromTitle') + security.declareProtected(Permissions.ModifyPortalContent, + 'getPublicationIdFromTitle') def getSubscriptionIdFromTitle(self, title): """ simply return an id from a title @@ -973,7 +1032,8 @@ class SynchronizationTool( SubscriptionSynchronization, PublicationSynchronizati """ # Import the conduit and get it from Products.ERP5SyncML import Conduit - conduit_module = __import__('.'.join([Conduit.__name__, conduit]), globals(), locals(), ['']) + conduit_module = __import__('.'.join([Conduit.__name__, conduit]), + globals(), locals(), ['']) conduit_object = getattr(conduit_module, conduit)() return conduit_object.addNode(**kw) diff --git a/product/ERP5SyncML/XMLSyncUtils.py b/product/ERP5SyncML/XMLSyncUtils.py index 706074a179..1ab6591ec0 100644 --- a/product/ERP5SyncML/XMLSyncUtils.py +++ b/product/ERP5SyncML/XMLSyncUtils.py @@ -47,7 +47,8 @@ from zLOG import LOG class XMLSyncUtilsMixin(SyncCode): def SyncMLHeader(self, session_id, msg_id, target, source, target_name=None, - source_name=None): + source_name=None, dataCred=None, authentication_format='b64', + authentication_type='syncml:auth-basic'): """ Since the Header is always almost the same, this is the way to set one quickly. @@ -61,14 +62,22 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <MsgID>%s</MsgID>\n' % msg_id) xml(' <Target>\n') xml(' <LocURI>%s</LocURI>\n' % target) - if target_name is not None: + if target_name not in (None, ''): xml(' <LocName>%s</LocName>\n' %target_name) xml(' </Target>\n') xml(' <Source>\n') xml(' <LocURI>%s</LocURI>\n' % source) - if source_name is not None: + if source_name not in (None, ''): xml(' <LocName>%s</LocName>\n' % source_name) xml(' </Source>\n') + if dataCred not in (None, ''): + xml(' <Cred>\n') + xml(' <Meta>\n') + xml(' <Format>%s</Format>\n' % authentication_format) + xml(' <Type>%s</Type>\n' % authentication_type) + xml(' </Meta>\n') + xml(' <Data>%s</Data>\n' % dataCred) + xml(' </Cred>\n') xml(' </SyncHdr>\n') xml_a = ''.join(xml_list) return xml_a @@ -103,7 +112,7 @@ class XMLSyncUtilsMixin(SyncCode): return xml_a def SyncMLStatus(self, cmd_id, target_ref, source_ref, sync_code, - next_anchor): + next_anchor=None): """ Since the Status section is always almost the same, this is the way to set one quickly. @@ -115,13 +124,14 @@ class XMLSyncUtilsMixin(SyncCode): xml(' <TargetRef>%s</TargetRef>\n' % target_ref) xml(' <SourceRef>%s</SourceRef>\n' % source_ref) xml(' <Data>%s</Data>\n' % sync_code) - xml(' <Item>\n') - xml(' <Data>\n') - xml(' <Anchor xmlns=\'syncml:metinf\'>\n') - xml(' <Next>%s</Next>\n' % next_anchor) - xml(' </Anchor>\n') - xml(' </Data>\n') - xml(' </Item>\n') + if next_anchor is not None: + xml(' <Item>\n') + xml(' <Data>\n') + xml(' <Anchor xmlns=\'syncml:metinf\'>\n') + xml(' <Next>%s</Next>\n' % next_anchor) + xml(' </Anchor>\n') + xml(' </Data>\n') + xml(' </Item>\n') xml(' </Status>\n') xml_a = ''.join(xml_list) return xml_a @@ -416,27 +426,6 @@ class XMLSyncUtilsMixin(SyncCode): return int(subnode.childNodes[0].data) return None - - #def getStatusCode(self, xml): - # """ - # Return the value of the alert code inside the xml_stream - # """ - # # Get informations from the body - # first_node = xml.childNodes[0] - # if first_node.nodeName != "SyncML": - # print "This is not a SyncML message" - # - # client_body = first_node.childNodes[3] - # if client_body.nodeName != "SyncBody": - # print "This is not a SyncML Body" - # - # for subnode in client_body.childNodes: - # if subnode.nodeName=='Status': - # for subnode2 in subnode.childNodes: - # if subnode2.nodeType == subnode.ELEMENT_NODE and subnode2.nodeName == 'Data': - # return int(subnode2.childNodes[0].data) - # return None - def getStatusCommand(self, xml): """ Return the value of the command inside the xml_stream @@ -448,6 +437,44 @@ class XMLSyncUtilsMixin(SyncCode): return subnode.childNodes[0].data return None + def getCred(self, xml): + """ + return the credential information : type, format and data + """ + format='' + type='' + data='' + + + first_node = xml.childNodes[0] + if first_node.nodeName != "SyncML": + print "This is not a SyncML message" + # Get informations from the header + xml_header = first_node.childNodes[1] + if xml_header.nodeName != "SyncHdr": + LOG('PubSyncModif',0,'This is not a SyncML Header') + raise ValueError, "Sorry, This is not a SyncML Header" + + for subnode in xml_header.childNodes: + if subnode.nodeType == subnode.ELEMENT_NODE and subnode.nodeName=='Cred': + for subnode2 in subnode.childNodes: + if subnode2.nodeType == subnode2.ELEMENT_NODE and \ + subnode2.nodeName == 'Meta': + for subnode3 in subnode2.childNodes: + if subnode3.nodeType == subnode3.ELEMENT_NODE and \ + subnode3.nodeName == 'Format': + if len(subnode3.childNodes) > 0: + format=subnode3.childNodes[0].data + if subnode3.nodeType == subnode3.ELEMENT_NODE and \ + subnode3.nodeName == 'Type': + if len(subnode3.childNodes) > 0: + type=subnode3.childNodes[0].data + if subnode2.nodeType == subnode2.ELEMENT_NODE and \ + subnode2.nodeName == 'Data': + if len(subnode2.childNodes) > 0: + data=subnode2.childNodes[0].data + return (format, type, data) + def getAlertCode(self, xml_stream): """ Return the value of the alert code inside the full syncml message @@ -739,13 +766,13 @@ class XMLSyncUtilsMixin(SyncCode): # object_gid = gid_generator() force = 0 if syncml_data.count('\n') < self.MAX_LINES and not object.id.startswith('.'): # If not we have to cut - LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.xml_mapping)) - LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml))) - LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list)) - LOG('getSyncMLData',0,'hasSignature: %s' % str(subscriber.hasSignature(object_gid))) - LOG('getSyncMLData',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC)) + #LOG('getSyncMLData',0,'xml_mapping: %s' % str(domain.xml_mapping)) + #LOG('getSyncMLData',0,'code: %s' % str(self.getAlertCode(remote_xml))) + #LOG('getSyncMLData',0,'gid_list: %s' % str(local_gid_list)) + #LOG('getSyncMLData',0,'hasSignature: %s' % str(subscriber.hasSignature(object_gid))) + #LOG('getSyncMLData',0,'alert_code == slowsync: %s' % str(self.getAlertCode(remote_xml)==self.SLOW_SYNC)) signature = subscriber.getSignature(object_gid) - LOG('getSyncMLData',0,'current object: %s' % str(object.getId())) + #LOG('getSyncMLData',0,'current object: %s' % str(object.getId())) # Here we first check if the object was modified or not by looking at dates if signature is not None: signature.checkSynchronizationNeeded(object) @@ -774,7 +801,7 @@ class XMLSyncUtilsMixin(SyncCode): rest_string = xml_string[len(short_string):] #LOG('XMLSyncUtils',0,'rest_string: %s' % str(rest_string)) i += 1 - LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string)) + #LOG('getSyncMLData',0,'setPartialXML with: %s' % str(rest_string)) signature.setPartialXML(rest_string) status =self.PARTIAL signature.setAction('Add') @@ -787,8 +814,8 @@ class XMLSyncUtilsMixin(SyncCode): elif signature.getStatus()==self.NOT_SYNCHRONIZED \ or signature.getStatus()==self.PUB_CONFLICT_MERGE: # We don't have synchronized this object yet xml_object = self.getXMLObject(object=object,xml_mapping=domain.xml_mapping) - LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object))) - LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus())) + #LOG('getSyncMLData',0,'checkMD5: %s' % str(signature.checkMD5(xml_object))) + #LOG('getSyncMLData',0,'getStatus: %s' % str(signature.getStatus())) if signature.getStatus()==self.PUB_CONFLICT_MERGE: xml_confirmation += self.SyncMLConfirmation(cmd_id,object.id, self.CONFLICT_MERGE,'Replace') @@ -821,7 +848,7 @@ class XMLSyncUtilsMixin(SyncCode): signature.setTempXML(xml_object) # Now we can apply the xupdate from the subscriber subscriber_xupdate = signature.getSubscriberXupdate() - LOG('getSyncMLData subscriber_xupdate',0,subscriber_xupdate) + #LOG('getSyncMLData subscriber_xupdate',0,subscriber_xupdate) if subscriber_xupdate is not None: old_xml = signature.getXML() conduit.updateNode(xml=subscriber_xupdate, object=object, @@ -1080,7 +1107,6 @@ class XMLSyncUtils(XMLSyncUtilsMixin): if xml_header.nodeName != "SyncHdr": LOG('PubSyncModif',0,'This is not a SyncML Header') raise ValueError, "Sorry, This is not a SyncML Header" - return subscriber = domain # If we are the client, this is fine simulate = 0 # used by applyActionList, should be 0 for client @@ -1103,11 +1129,12 @@ class XMLSyncUtils(XMLSyncUtilsMixin): if last_xml != '': has_response = 1 if domain.domain_type == self.PUB: # We always reply - self.sendResponse(from_url=domain.publication_url, to_url=subscriber.subscription_url, - sync_id=domain.getTitle(), xml=last_xml,domain=domain) + self.sendResponse(from_url=domain.publication_url, + to_url=subscriber.subscription_url, sync_id=domain.getTitle(), + xml=last_xml,domain=domain) elif domain.domain_type == self.SUB: - self.sendResponse(from_url=domain.subscription_url, to_url=domain.publication_url, - sync_id=domain.getTitle(), xml=last_xml,domain=domain) + self.sendResponse(from_url=domain.subscription_url, + to_url=domain.publication_url, sync_id=domain.getTitle(), xml=last_xml,domain=domain) return {'has_response':has_response,'xml':last_xml} subscriber.setLastSentMessage('') @@ -1135,7 +1162,7 @@ class XMLSyncUtils(XMLSyncUtilsMixin): subscriber=subscriber, remote_xml=remote_xml, conduit=conduit, simulate=simulate) - LOG('SyncModif, has_next_action:',0,has_next_action) + #LOG('SyncModif, has_next_action:',0,has_next_action) xml_list = [] xml = xml_list.append diff --git a/product/ERP5SyncML/dtml/managePublications.dtml b/product/ERP5SyncML/dtml/managePublications.dtml index 993d1b714e..afd939bebf 100644 --- a/product/ERP5SyncML/dtml/managePublications.dtml +++ b/product/ERP5SyncML/dtml/managePublications.dtml @@ -127,6 +127,36 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="gid_generator" value="<dtml-var getGidGenerator>" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Authentication Required + </label></div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="auth_required" value="1" <dtml-if expr="isAuthenticationRequired()">CHECKED</dtml-if>> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Format authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_format" value="<dtml-var getAuthenticationFormat>" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Type authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_type" value="<dtml-var getAuthenticationType>" size="40" /> + </td> + </tr> </table> <table> <tr> diff --git a/product/ERP5SyncML/dtml/manageSubscriptions.dtml b/product/ERP5SyncML/dtml/manageSubscriptions.dtml index 7adabdcfe5..fb7bd87a3f 100644 --- a/product/ERP5SyncML/dtml/manageSubscriptions.dtml +++ b/product/ERP5SyncML/dtml/manageSubscriptions.dtml @@ -137,6 +137,46 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="gid_generator" value="<dtml-var getGidGenerator>" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Login + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="login" value="<dtml-var getLogin>" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Password + </label></div> + </td> + <td align="left" valign="top"> + <input type="password" name="password" value="<dtml-var getPassword>" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Format authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_format" value="<dtml-var getAuthenticationFormat>" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Type authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_type" value="<dtml-var getAuthenticationType>" size="40" /> + </td> + </tr> </table> <table> <tr> diff --git a/product/ERP5SyncML/dtml/manage_addPublication.dtml b/product/ERP5SyncML/dtml/manage_addPublication.dtml index db3ef71d14..8b59f8ee75 100644 --- a/product/ERP5SyncML/dtml/manage_addPublication.dtml +++ b/product/ERP5SyncML/dtml/manage_addPublication.dtml @@ -123,6 +123,36 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. <input type="text" name="gid_generator" size="40" /> </td> </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Authentication Required + </label></div> + </td> + <td align="left" valign="top"> + <input type="checkbox" name="auth_required" value="1"> + </td> + </tr> + <tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Format authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_format" size="40" /> + </td> + </tr> + <td align="left" valign="top"> + <div class="form-label"> + Type authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_type" size="40" /> + </td> + </tr> <tr> <td align="left" valign="top"> </td> diff --git a/product/ERP5SyncML/dtml/manage_addSubscription.dtml b/product/ERP5SyncML/dtml/manage_addSubscription.dtml index 6ea3a19b77..50a5a2df64 100644 --- a/product/ERP5SyncML/dtml/manage_addSubscription.dtml +++ b/product/ERP5SyncML/dtml/manage_addSubscription.dtml @@ -134,6 +134,45 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. </td> </tr> <tr> + <td align="left" valign="top"> + <div class="form-label"> + Login + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="login" size="40" /> </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Password + </label></div> + </td> + <td align="left" valign="top"> + <input type="password" name="password" size="40" /> + </td> + </tr> + <tr> + <td align="left" valign="top"> + <div class="form-label"> + Format authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_format" size="40" /> + </td> + </tr> + <td align="left" valign="top"> + <div class="form-label"> + Type authentication + </label></div> + </td> + <td align="left" valign="top"> + <input type="text" name="authentication_type" size="40" /> + </td> + </tr> + <tr> + <tr> <td align="left" valign="top"> </td> <td align="left" valign="top"> diff --git a/product/ERP5SyncML/tests/testERP5SyncML.py b/product/ERP5SyncML/tests/testERP5SyncML.py index 53be549c63..bc5a8ca912 100644 --- a/product/ERP5SyncML/tests/testERP5SyncML.py +++ b/product/ERP5SyncML/tests/testERP5SyncML.py @@ -42,6 +42,10 @@ from Products.ERP5SyncML.Conduit.ERP5Conduit import ERP5Conduit from Products.ERP5SyncML.SyncCode import SyncCode from zLOG import LOG +try: + from base64 import b64encode, b64decode +except ImportError: + from base64 import encodestring as b64encode, decodestring as b64decode class TestERP5SyncML(ERP5TypeTestCase): # Different variables used for this test @@ -51,7 +55,7 @@ class TestERP5SyncML(ERP5TypeTestCase): last_name1 = 'Robin' # At the beginning, I was using iso-8859-15 strings, but actually # erp5 is using utf-8 everywhere - #description1 = 'description1 --- $sdfr鏮sdfs鏳f_oisfsopf' + #description1 = 'description1 --- $sdfr莽_sdfs莽df_oisfsopf' description1 = 'description1 --- $sdfr\xc3\xa7_sdfs\xc3\xa7df_oisfsopf' lang1 = 'fr' format2 = 'html' @@ -59,12 +63,12 @@ class TestERP5SyncML(ERP5TypeTestCase): format4 = 'txt' first_name2 = 'Jean-Paul' last_name2 = 'Smets' - #description2 = 'description2猷@ $*< <<< ----- >>>></title>&oekd' + #description2 = 'description2茅脿@ $*< <<< ----- >>>></title>&oekd' description2 = 'description2\xc3\xa9\xc3\xa0@ $*< <<< ----- >>>></title>&oekd' lang2 = 'en' first_name3 = 'Yoshinori' last_name3 = 'Okuji' - #description3 = 'description3 鐂df__sdf珑鏮df___&&閉]]鞍鞍鞍' + #description3 = 'description3 莽sdf__sdf莽莽莽_df___&&茅]]]掳掳掳掳掳掳' description3 = 'description3 \xc3\xa7sdf__sdf\xc3\xa7\xc3\xa7\xc3\xa7_df___&&\xc3\xa9]]]\xc2\xb0\xc2\xb0\xc2\xb0\xc2\xb0\xc2\xb0\xc2\xb0' #description4 = 'description4 sdflkmooo^^^^]]]]]{{{{{{{' description4 = 'description4 sdflkmooo^^^^]]]]]{{{{{{{' @@ -182,10 +186,10 @@ class TestERP5SyncML(ERP5TypeTestCase): def login(self, quiet=0): uf = self.getPortal().acl_users - uf._doAddUser('seb', '', ['Manager'], []) + uf._doAddUser('fab', 'myPassword', ['Manager'], []) uf._doAddUser('ERP5TypeTestCase', '', ['Manager'], []) uf._doAddUser('syncml', '', ['Manager'], []) - user = uf.getUserById('seb').__of__(uf) + user = uf.getUserById('fab').__of__(uf) newSecurityManager(None, user) def populatePersonServer(self, quiet=0, run=run_all_test): @@ -317,7 +321,6 @@ class TestERP5SyncML(ERP5TypeTestCase): to define it here because it is specific to the unit testing """ portal_sync = self.getSynchronizationTool() - #portal_sync.email = None # XXX To be removed subscription = portal_sync.getSubscription(id) publication = None for publication in portal_sync.getPublicationList(): @@ -984,7 +987,7 @@ class TestERP5SyncML(ERP5TypeTestCase): self.failUnless(person_s.getDescription()==self.description3) self.failUnless(person_c1.getDescription()==self.description3) - def test_25_MultiNodeConflict(self, quiet=0, run=run_all_test): + def test_25_MultiNodeConflict(self, quiet=0, run=1): """ We will create conflicts with 3 differents nodes, and we will solve it by taking one full version of documents. @@ -1097,9 +1100,9 @@ class TestERP5SyncML(ERP5TypeTestCase): person_client1 = self.getPersonClient1() person1_c = person_client1._getOb(self.id1) person2_c = person_client1._getOb(self.id2) - person1_s.manage_setLocalRoles('seb',['Manager','Owner']) + person1_s.manage_setLocalRoles('fab',['Manager','Owner']) person2_s.manage_setLocalRoles('jp',['Manager','Owner']) - person2_s.manage_delLocalRoles(['seb']) + person2_s.manage_delLocalRoles(['fab']) self.synchronize(self.sub_id1) self.synchronize(self.sub_id2) role_1_s = person1_s.get_local_roles() @@ -1182,7 +1185,7 @@ class TestERP5SyncML(ERP5TypeTestCase): def test_30_GetSynchronizationType(self, quiet=0, run=run_all_test): # We will try to update some simple data, first - # we change on the server side, the on the client side + # we change on the server side, then on the client side if not run: return if not quiet: ZopeTestCase._print('\nTest Get Synchronization Type ') @@ -1257,20 +1260,20 @@ class TestERP5SyncML(ERP5TypeTestCase): self.assertEqual(role_1_s,role_1_c) self.assertEqual(role_2_s,role_2_c) - def test_32_AddOneWaySubscription(self, quiet=0, run=1): + def test_32_AddOneWaySubscription(self, quiet=0, run=run_all_test): if not run: return if not quiet: ZopeTestCase._print('\nTest Add One Way Subscription ') LOG('Testing... ',0,'test_32_AddOneWaySubscription') portal_id = self.getPortalId() portal_sync = self.getSynchronizationTool() - portal_sync.manage_addSubscription(self.sub_id1,self.publication_url, - self.subscription_url1,'/%s/person_client1' % portal_id,'objectValues', - '','ERP5Conduit','') + portal_sync.manage_addSubscription(self.sub_id1, self.publication_url, + self.subscription_url1, '/%s/person_client1' % portal_id, + 'objectValues', '', 'ERP5Conduit', '') sub = portal_sync.getSubscription(self.sub_id1) self.failUnless(sub is not None) - def test_33_OneWaySync(self, quiet=0, run=1): + def test_33_OneWaySync(self, quiet=0, run=run_all_test): """ We will test if we can synchronize only from to server to the client. We want to make sure in this case that all modifications on the client @@ -1314,6 +1317,234 @@ class TestERP5SyncML(ERP5TypeTestCase): self.assertEquals(person1_s.getFirstName(),self.first_name1) + def test_34_encoding(self, quiet=0, run=run_all_test): + """ + We will test if we can encode strings with b64encode to encode + the login and password for authenticated sessions + """ + #when there will be other format implemented with encode method, + #there will be tested here + + if not run: return + self.test_08_FirstSynchronization(quiet=1,run=1) + if not quiet: + ZopeTestCase._print('\nTest Strings Encoding ') + LOG('Testing... ',0,'test_34_encoding') + + #define some strings : + python = 'www.python.org' + awaited_result_python = "d3d3LnB5dGhvbi5vcmc=" + long_string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO\ +PQRSTUVWXYZ茅猫莽脿@^~碌&虏0123456789!@#0^&*();:<>,. []{}\xc3\xa7sdf__\ +sdf\xc3\xa7\xc3\xa7\xc3\xa7_df___&&\xc3\xa9]]]\xc2\xb0\xc2\xb0\xc2\ +\xb0\xc2\xb0\xc2\xb0\xc2\xb0" +#= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ茅猫莽脿@^~碌&虏012345 +#6789!@#0^&*();:<>,. []{}莽sdf__sdf莽莽莽_df___&&茅]]]掳掳掳掳掳掳'" + + awaited_result_long_string = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZH\ +SElKS0xNTk9QUVJTVFVWV1hZWsOpw6jDp8OgQF5+wrUmwrIwMTIzNDU2Nzg5IUAjMF4mKigpOzo8Pi\ +wuIFtde33Dp3NkZl9fc2Rmw6fDp8OnX2RmX19fJibDqV1dXcKwwrDCsMKwwrDCsA==' + + #test just b64encode + self.assertEqual(b64encode(python), awaited_result_python) + self.assertEqual(b64encode(""), "") + self.assertEqual(b64encode(long_string), awaited_result_long_string) + + self.assertEqual(b64decode(awaited_result_python), python) + self.assertEqual(b64decode(""), "") + self.assertEqual(b64decode(awaited_result_long_string), long_string) + + # test with the ERP5 functions + portal_sync = self.getSynchronizationTool() + publication = portal_sync.getPublication(self.pub_id) + subscription1 = portal_sync.getSubscription(self.sub_id1) + + string_encoded = subscription1.encode('b64', python) + self.assertEqual(string_encoded, awaited_result_python) + string_decoded = subscription1.decode('b64', awaited_result_python) + self.assertEqual(string_decoded, python) + self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded, + python, 'b64')) + self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded, + string_decoded, 'b64')) + + string_encoded = subscription1.encode('b64', long_string) + self.assertEqual(string_encoded, awaited_result_long_string) + string_decoded = subscription1.decode('b64', awaited_result_long_string) + self.assertEqual(string_decoded, long_string) + self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded, + long_string, 'b64')) + self.failUnless(subscription1.isDecodeEncodeTheSame(string_encoded, + string_decoded, 'b64')) + + self.assertEqual(subscription1.encode('b64', ''), '') + self.assertEqual(subscription1.decode('b64', ''), '') + self.failUnless(subscription1.isDecodeEncodeTheSame( + subscription1.encode('b64', ''), '', 'b64')) + + def addAuthenticationToPublication(self, publication_id, login, password, + auth_format, auth_type): + """ + add authentication to the publication + """ + portal_sync = self.getSynchronizationTool() + pub = portal_sync.getPublication(publication_id) + pub.setAuthentication(True) + pub.setLogin(login) + pub.setPassword(password) + pub.setAuthenticationFormat(auth_format) + pub.setAuthenticationType(auth_type) + + + def addAuthenticationToSubscription(self, subscription_id, login, password, + auth_format, auth_type): + """ + add authentication to the subscription + """ + portal_sync = self.getSynchronizationTool() + sub = portal_sync.getSubscription(subscription_id) + sub.setAuthentication(True) + sub.setAuthenticated(False) + sub.setLogin(login) + sub.setPassword(password) + sub.setAuthenticationFormat(auth_format) + sub.setAuthenticationType(auth_type) + + def verifyFirstNameAndLastNameAreSynchronized(self, first_name, + last_name, person_server, person_client): + """ + verify if the first and last name are synchronized + """ + self.failUnless(person_server.getFirstName()==first_name) + self.failUnless(person_server.getLastName()==last_name) + self.failUnless(person_client.getFirstName()==first_name) + self.failUnless(person_client.getLastName()==last_name) + + def verifyFirstNameAndLastNameAreNotSynchronized(self, first_name, + last_name, person_server, person_client): + """ + verify that the first and last name are NOT synchronized + """ + self.failUnless(person_server.getFirstName()!=first_name) + self.failUnless(person_server.getLastName()!=last_name) + self.failUnless(person_client.getFirstName()==first_name) + self.failUnless(person_client.getLastName()==last_name) + + def test_35_authentication(self, quiet=0, run=1): + """ + we will test + - if we can't synchronize without good authentication for an + autentication required publication. + - if we can synchronize without of with (and bad or good) authentication + for an not required autentication publication + """ + + if not run: return + if not quiet: + ZopeTestCase._print('\nTest Authentication ') + LOG('Testing... ',0,'test_35_authentication') + + self.test_08_FirstSynchronization(quiet=1,run=1) + # First we do only modification on client + portal_sync = self.getSynchronizationTool() + person_server = self.getPersonServer() + person1_s = person_server._getOb(self.id1) + person_client1 = self.getPersonClient1() + person1_c = person_client1._getOb(self.id1) + + kw = {'first_name':self.first_name3,'last_name':self.last_name3} + person1_c.edit(**kw) + + #check that it's not synchronize + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3, + self.last_name3, person1_s, person1_c) + self.synchronize(self.sub_id1) + #now it should be synchronize + self.checkSynchronizationStateIsSynchronized() + self.verifyFirstNameAndLastNameAreSynchronized(self.first_name3, + self.last_name3, person1_s, person1_c) + + + #adding authentication : + self.addAuthenticationToPublication(self.pub_id, 'fab', 'myPassword', 'b64', + 'syncml:auth-basic') + # try to synchronize without authentication on the subscription, it + # should failed + kw = {'first_name':self.first_name2,'last_name':self.last_name2} + person1_c.edit(**kw) + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name2, + self.last_name2, person1_s, person1_c) + # here, before and after synchronization, the person1_s shoudn't have + # the name as the person1_c because the user isn't authenticated + self.synchronize(self.sub_id1) + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name2, + self.last_name2, person1_s, person1_c) + + #try to synchronize whith an authentication on both the client and server + self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'myPassword', + 'b64', 'syncml:auth-basic') + #now it should be correctly synchronize + self.synchronize(self.sub_id1) + self.checkSynchronizationStateIsSynchronized() + self.verifyFirstNameAndLastNameAreSynchronized(self.first_name2, + self.last_name2, person1_s, person1_c) + + #try to synchronize with a bad login and/or password + #test if login is case sensitive (it should be !) + self.addAuthenticationToSubscription(self.sub_id1, 'fAb', 'myPassword', + 'b64', 'syncml:auth-basic') + kw = {'first_name':self.first_name1,'last_name':self.last_name1} + person1_c.edit(**kw) + self.synchronize(self.sub_id1) + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name1, + self.last_name1, person1_s, person1_c) + + #with a paswword case sensitive + self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'mypassword', + 'b64', 'syncml:auth-basic') + kw = {'first_name':self.first_name1,'last_name':self.last_name1} + person1_c.edit(**kw) + self.synchronize(self.sub_id1) + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name1, + self.last_name1, person1_s, person1_c) + + + #with the good password + self.addAuthenticationToSubscription(self.sub_id1, 'fab', 'myPassword', + 'b64', 'syncml:auth-basic') + #now it should be correctly synchronize + self.synchronize(self.sub_id1) + self.checkSynchronizationStateIsSynchronized() + self.verifyFirstNameAndLastNameAreSynchronized(self.first_name1, + self.last_name1, person1_s, person1_c) + + #verify that the login and password with utf8 caracters are accecpted + + # add a user with an utf8 login + uf = self.getPortal().acl_users + uf._doAddUser('\xc3\xa9pouet', 'ploum', ['Manager'], []) # \xc3\xa9pouet = 茅pouet + user = uf.getUserById('\xc3\xa9pouet').__of__(uf) + newSecurityManager(None, user) + + self.addAuthenticationToPublication(self.pub_id, '\xc3\xa9pouet', 'ploum', + 'b64', 'syncml:auth-basic') + #first, try with a wrong login : + self.addAuthenticationToSubscription(self.sub_id1, 'pouet', 'ploum', + 'b64', 'syncml:auth-basic') + kw = {'first_name':self.first_name3,'last_name':self.last_name3} + person1_c.edit(**kw) + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3, + self.last_name3, person1_s, person1_c) + self.synchronize(self.sub_id1) + self.verifyFirstNameAndLastNameAreNotSynchronized(self.first_name3, + self.last_name3, person1_s, person1_c) + #now with the good : + self.addAuthenticationToSubscription(self.sub_id1, '\xc3\xa9pouet', 'ploum', + 'b64', 'syncml:auth-basic') + self.synchronize(self.sub_id1) + self.verifyFirstNameAndLastNameAreSynchronized(self.first_name3, + self.last_name3, person1_s, person1_c) + self.checkSynchronizationStateIsSynchronized() if __name__ == '__main__': framework() -- 2.30.9