PublicationSynchronization.py 12.6 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
##############################################################################
#
# Copyright (c) 2003 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

import smtplib # to send emails
from Publication import Publication,Subscriber
from Subscription import Signature
32
from XMLSyncUtils import Parse
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33 34
from XMLSyncUtils import XMLSyncUtils
from Conduit.ERP5Conduit import ERP5Conduit
35
from Products.CMFCore.utils import getToolByName
36 37 38 39
from Products.ERP5Security.ERP5UserManager import ERP5UserManager
from Products.PluggableAuthService.interfaces.plugins import\
    IAuthenticationPlugin
from AccessControl.SecurityManagement import newSecurityManager
Jean-Paul Smets's avatar
Jean-Paul Smets committed
40 41 42 43
import commands
from zLOG import LOG

class PublicationSynchronization(XMLSyncUtils):
44 45 46
  """
    
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
47

Sebastien Robin's avatar
Sebastien Robin committed
48
  def PubSyncInit(self, publication=None, xml_client=None, subscriber=None, 
49
      sync_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
50 51 52 53
    """
      Read the client xml message
      Send the first XML message from the server
    """
54
    #LOG('PubSyncInit',0,'Starting... publication: %s' % str(publication))
55
    
Sebastien Robin's avatar
Sebastien Robin committed
56 57
    #the session id is set at the same value of those of the client
    subscriber.setSessionId(self.getSessionId(xml_client))
58 59 60 61
    #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)
Sebastien Robin's avatar
Sebastien Robin committed
62 63 64
    #the last_message_id is 1 because the message that 
    #we are about to send is the message 1      
    subscriber.initLastMessageId(1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
65 66 67 68 69 70 71 72

    alert = None
    # Get informations from the body
    if xml_client is not None: # We have received a message
      last_anchor = self.getAlertLastAnchor(xml_client)
      next_anchor = self.getAlertNextAnchor(xml_client)
      alert = self.checkAlert(xml_client)
      alert_code = self.getAlertCode(xml_client)
Sebastien Robin's avatar
Sebastien Robin committed
73 74
      cred = self.checkCred(xml_client)
      #XXX this is in developement, it's just for tests
75 76
      if publication.isAuthenticationRequired():
        if not cred:
77
          #LOG('PubSyncInit',0,'authentication required')
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
	        # 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)
113
              if decoded not in ('', None) and ':' in decoded:
114 115 116 117 118 119 120 121 122 123 124 125 126
                (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
127 128
                  else:
                    auth_code=self.UNAUTHORIZED
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
          #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))
146 147 148 149 150 151 152
          cmd_id += 1
          if auth_code == self.AUTH_ACCEPTED:
            # alert message
            xml(self.SyncMLAlert(cmd_id, sync_type, 
              subscriber.getSubscriptionUrl(), publication.getPublicationUrl(),
              subscriber.getLastAnchor(), subscriber.getNextAnchor()))
            cmd_id += 1
153 154 155 156 157 158 159
          else:
	          # chal message
            xml(self.SyncMLChal(cmd_id, "SyncHdr", 
              publication.getPublicationUrl(), subscriber.getSubscriptionUrl(), 
              publication.getAuthenticationFormat(), 
              publication.getAuthenticationType(), self.AUTH_REQUIRED))
            cmd_id += 1
160 161 162 163 164 165 166 167
          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)

Sebastien Robin's avatar
Sebastien Robin committed
168 169 170 171 172 173 174 175 176
      else :
        # If slow sync, then resend everything
        if alert_code == self.SLOW_SYNC:
          LOG('Warning !!!, reseting client synchronization for subscriber:',0,
              subscriber)
          subscriber.resetAllSignatures()

        # Check if the last time synchronization is the same as the client one
        mess='\nsubscriber.getNextAnchor:\t%s\nsubscriber.getLastAnchor:\t%s\
177
        \nlast_anchor:\t\t\t%s\nnext_anchor:\t\t\t%s' % (subscriber.getNextAnchor(), subscriber.getLastAnchor(), last_anchor, next_anchor)
178
        #LOG('PubSyncInit',0,mess)
Sebastien Robin's avatar
Sebastien Robin committed
179 180 181
        
        if subscriber.getNextAnchor() != last_anchor:
          if last_anchor == None:
182
            #LOG('PubSyncInit',0,'anchor null')
Sebastien Robin's avatar
Sebastien Robin committed
183 184
            raise ValueError, "Sorry, the anchor was null"
          else:
185 186
            message = "bad anchors in PubSyncInit! " + \
                subscriber.getNextAnchor() + " and " + last_anchor
187
            #LOG('PubSyncInit',0,message)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
188
        else:
Sebastien Robin's avatar
Sebastien Robin committed
189
	        subscriber.setNextAnchor(next_anchor)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
190 191 192 193 194 195
      # We have to set every object as NOT_SYNCHRONIZED
      subscriber.startSynchronization()
    else:
      # We have started the sync from the server (may be for a conflict resolution)
      pass

196
    if alert is not None and not publication.isAuthenticationRequired():
Jean-Paul Smets's avatar
Jean-Paul Smets committed
197 198
      # Prepare the xml message for the Sync initialization package
      cmd_id = 1 # specifies a SyncML message-unique command identifier
Sebastien Robin's avatar
Sebastien Robin committed
199 200
      xml_list = []
      xml = xml_list.append
Jean-Paul Smets's avatar
Jean-Paul Smets committed
201

Sebastien Robin's avatar
Sebastien Robin committed
202
      xml('<SyncML>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
203
      # syncml header
Sebastien Robin's avatar
Sebastien Robin committed
204 205 206
      xml(self.SyncMLHeader(subscriber.getSessionId(), 
        subscriber.incrementMessageId(), subscriber.getSubscriptionUrl(), 
        publication.getPublicationUrl()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
207
      # syncml body
Sebastien Robin's avatar
Sebastien Robin committed
208
      xml(' <SyncBody>\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
209
      # alert message
Sebastien Robin's avatar
Sebastien Robin committed
210 211 212
      xml(self.SyncMLAlert(cmd_id, sync_type, subscriber.getSubscriptionUrl(),
        publication.getPublicationUrl(), subscriber.getLastAnchor(), 
        subscriber.getNextAnchor()))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
213
      cmd_id += 1
Sebastien Robin's avatar
Sebastien Robin committed
214 215 216
      xml(' </SyncBody>\n')
      xml('</SyncML>\n')
      xml_a = ''.join(xml_list)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
217

Sebastien Robin's avatar
Sebastien Robin committed
218 219 220 221
      self.sendResponse(from_url=publication.getPublicationUrl(),
        to_url=subscriber.getSubscriptionUrl(), sync_id=publication.getTitle(), 
        xml=xml_a, domain=publication)
    return {'has_response':1,'xml':xml_a}
Jean-Paul Smets's avatar
Jean-Paul Smets committed
222 223


224
  def PubSync(self, publication, msg=None, RESPONSE=None, subscriber=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
225 226 227
    """
      This is the synchronization method for the server
    """
228
    #LOG('PubSync',0,'Starting... id: %s' % str(id))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
229
    # Read the request from the client
230
    xml_client = msg
231
    publication
232
    if xml_client is None:
233 234
      xml_client = self.readResponse(from_url=publication.getPublicationUrl())
    #LOG('PubSync',0,'Starting... msg: %s' % str(xml_client))
235
    result = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
236 237

    if xml_client is not None:
Sebastien Robin's avatar
Sebastien Robin committed
238
      if isinstance(xml_client, str) or isinstance(xml_client, unicode):
239
        xml_client = Parse(xml_client)
Sebastien Robin's avatar
Sebastien Robin committed
240
      first_node = xml_client.childNodes[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
241 242

      if first_node.nodeName != "SyncML":
243
        #LOG('PubSync',0,'This is not a SyncML Message')
Sebastien Robin's avatar
Sebastien Robin committed
244
        raise ValueError, "Sorry, This is not a SyncML Message"
245
      alert_code = self.getAlertCode(xml_client)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
246 247 248 249

      # Get informations from the header
      client_header = first_node.childNodes[1]
      if client_header.nodeName != "SyncHdr":
250
        #LOG('PubSync',0,'This is not a SyncML Header')
Sebastien Robin's avatar
Sebastien Robin committed
251
        raise ValueError, "Sorry, This is not a SyncML Header"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
252
      for subnode in client_header.childNodes:
Sebastien Robin's avatar
Sebastien Robin committed
253 254 255 256 257 258
        if subnode.nodeType == subnode.ELEMENT_NODE and \
            subnode.nodeName == "Source":
          for subnode2 in subnode.childNodes:
            if subnode2.nodeType == subnode2.ELEMENT_NODE and \
                subnode2.nodeName == "LocURI":
              subscription_url = str(subnode2.childNodes[0].data)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
259
      # Get the subscriber or create it if not already in the list
260
      subscriber = publication.getSubscriber(subscription_url)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
261
      if subscriber == None:
262
        subscriber = Subscriber(publication.generateNewId(),subscription_url)
263
        subscriber.setXMLMapping(publication.getXMLMapping())
264
        publication.addSubscriber(subscriber)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
265
        # first synchronization
Sebastien Robin's avatar
Sebastien Robin committed
266 267
        result = self.PubSyncInit(publication,xml_client,subscriber=subscriber,
            sync_type=self.SLOW_SYNC)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
268

269

Sebastien Robin's avatar
Sebastien Robin committed
270 271 272 273
      elif self.checkAlert(xml_client) and \
          alert_code in (self.TWO_WAY,self.SLOW_SYNC):
        result = self.PubSyncInit(publication=publication, 
            xml_client=xml_client, subscriber=subscriber, sync_type=alert_code)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
274
      else:
275 276 277 278 279 280 281 282 283
        #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)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
284 285 286
    elif subscriber is not None:
      # This looks like we are starting a synchronization after
      # a conflict resolution by the user
Sebastien Robin's avatar
Sebastien Robin committed
287 288
      result = self.PubSyncInit(publication=publication, xml_client=None, 
          subscriber=subscriber,sync_type=self.TWO_WAY)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
289 290 291

    if RESPONSE is not None:
      RESPONSE.redirect('managePublications')
292 293
    elif result is not None:
      return result
Jean-Paul Smets's avatar
Jean-Paul Smets committed
294 295 296 297 298

  def PubSyncModif(self, publication, xml_client):
    """
    The modidification message for the publication
    """
299
    return self.SyncModif(publication,xml_client)