PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/indico/MaKaC/plugins/Collaboration/EVO/common.py

https://github.com/davidmorrison/indico
Python | 288 lines | 257 code | 12 blank | 19 comment | 6 complexity | e8c8032f1741b07a544554f23c9e4550 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. ##
  3. ##
  4. ## This file is part of CDS Indico.
  5. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
  6. ##
  7. ## CDS Indico is free software; you can redistribute it and/or
  8. ## modify it under the terms of the GNU General Public License as
  9. ## published by the Free Software Foundation; either version 2 of the
  10. ## License, or (at your option) any later version.
  11. ##
  12. ## CDS Indico is distributed in the hope that it will be useful, but
  13. ## WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. ## General Public License for more details.
  16. ##
  17. ## You should have received a copy of the GNU General Public License
  18. ## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
  19. ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  20. from MaKaC.plugins.Collaboration.base import CollaborationException, CSErrorBase
  21. from urllib2 import HTTPError, URLError, urlopen
  22. from datetime import timedelta
  23. from BaseHTTPServer import BaseHTTPRequestHandler
  24. from MaKaC.common.url import URL
  25. from array import array
  26. from MaKaC.common.timezoneUtils import nowutc, datetimeToUnixTime
  27. from MaKaC.common.logger import Logger
  28. from MaKaC.plugins.Collaboration.collaborationTools import CollaborationTools
  29. from MaKaC.common.fossilize import Fossilizable, fossilizes
  30. from MaKaC.plugins.Collaboration.EVO.fossils import IEVOWarningFossil,\
  31. IEVOErrorFossil, IOverlappedErrorFossil, IChangesFromEVOErrorFossil
  32. readLimit = 100000;
  33. secondsToWait = 10;
  34. encodingTextStart = '<fmt:requestEncoding value='
  35. encodingTextEnd = '/>'
  36. def getEVOOptionValueByName(optionName):
  37. return CollaborationTools.getOptionValue('EVO', optionName)
  38. def getActionURL(actionString):
  39. EVOServerURL = getEVOOptionValueByName("httpServerLocation")
  40. actionServlet = getEVOOptionValueByName("APIMap")[actionString]
  41. if EVOServerURL.endswith('/'):
  42. return EVOServerURL + actionServlet
  43. else:
  44. return EVOServerURL + '/' + actionServlet
  45. def getRequestURL(action, arguments = {}):
  46. actionURL = getActionURL(action)
  47. indicoID = getEVOOptionValueByName("indicoUserID")
  48. indicoPassword = getEVOOptionValueByName("indicoPassword")
  49. expirationTime = int(datetimeToUnixTime(nowutc() + timedelta(minutes = getEVOOptionValueByName('expirationTime'))) * 1000)
  50. arguments["from"] = createLoginKey(indicoID, indicoPassword, expirationTime)
  51. url = URL(actionURL)
  52. url.setParams(arguments)
  53. url.setSeparator('&')
  54. return url
  55. def getEVOAnswer(action, arguments = {}, eventId = '', bookingId = ''):
  56. url = getRequestURL(action, arguments)
  57. Logger.get('EVO').info("""Evt:%s, booking:%s, sending request to EVO: [%s]""" % (eventId, bookingId, str(url)))
  58. try:
  59. answer = urlopen(str(url), timeout=secondsToWait).read(readLimit).strip() #we remove any whitespaces, etc. We won't read more than 100k characters
  60. Logger.get('EVO').info("""Evt:%s, booking:%s, got answer (unprocessed): [%s]""" % (eventId, bookingId, str(answer)))
  61. except HTTPError, e:
  62. code = e.code
  63. shortMessage = BaseHTTPRequestHandler.responses[code][0]
  64. longMessage = BaseHTTPRequestHandler.responses[code][1]
  65. Logger.get('EVO').error("""Evt:%s, booking:%s, request: [%s] triggered HTTPError: %s (code = %s, shortMessage = '%s', longMessage = '%s'""" % (eventId, bookingId, str(url), str(e), code, shortMessage, longMessage))
  66. if str(code) == '404':
  67. raise EVOException('Indico could not find the EVO Server at ' + getEVOOptionValueByName("httpServerLocation") + "(HTTP error 404)")
  68. elif str(code) == '500':
  69. raise EVOException("The EVO server has an internal problem (HTTP error 500)", e)
  70. else:
  71. raise EVOException("""Problem when Indico tried to contact the EVO Server.\nReason: HTTPError: %s (code = %s, shortMessage = '%s', longMessage = '%s', url = '%s'""" % (str(e), code, shortMessage, longMessage, str(url)), e)
  72. except URLError, e:
  73. Logger.get('EVO').error("""Evt:%s, booking:%s, request: [%s] triggered exception: %s""" % (eventId, bookingId, str(url), str(e)))
  74. if str(e.reason).strip() == 'timed out':
  75. raise EVOException("The EVO server is not responding.", e)
  76. raise EVOException('URLError when contacting the EVO server for action: ' + action + '. Reason="' + str(e.reason)+'"', e)
  77. else: #we parse the answer
  78. encodingTextStart = '<fmt:requestEncoding value='
  79. encodingTextEnd = '/>'
  80. answer = answer.strip()
  81. #we parse an eventual encoding specification, like <fmt:requestEncoding value="UTF-8"/>
  82. if answer.startswith(encodingTextStart):
  83. endOfEncondingStart = answer.find(encodingTextStart) + len(encodingTextStart)
  84. endOfEncodingEnd = answer.find(encodingTextEnd) + len(encodingTextEnd)
  85. valueStartIndex = max(answer.find('"', endOfEncondingStart, endOfEncodingEnd), answer.find("'", endOfEncondingStart, endOfEncodingEnd)) + 1 #find returns -1 if not found
  86. valueEndIndex = max(answer.find('"', valueStartIndex, endOfEncodingEnd), answer.find("'", valueStartIndex, endOfEncodingEnd))
  87. encoding = answer[valueStartIndex:valueEndIndex].strip()
  88. answer = answer[endOfEncodingEnd:].strip()
  89. answer = answer.decode(encoding).encode('utf-8')
  90. if answer.startswith("OK:"):
  91. answer = answer[3:].strip() #we remove the "OK:"
  92. Logger.get('EVO').info("""Evt:%s, booking:%s, got answer (processed): [%s]""" % (eventId, bookingId, str(answer)))
  93. return answer
  94. elif answer.startswith("ERROR:"):
  95. error = answer[6:].strip()
  96. Logger.get('EVO').warning("""Evt:%s, booking:%s, request: [%s] triggered EVO error: %s""" % (eventId, bookingId, str(url), error))
  97. if error == 'YOU_ARE_NOT_OWNER_OF_THE_MEETING' or error == 'NOT_AUTHORIZED_SERVER' or error == 'NOT_AUTHORIZED' or\
  98. error == 'LOGIN_KEY_WRONG_LENGTH':
  99. raise EVOException("Indico's EVO ID / pwd do not seem to be right. Please report to Indico support.", error)
  100. elif error == 'REQUEST_EXPIRED':
  101. raise EVOException("Problem contacting EVO: REQUEST_EXPIRED", 'REQUEST_EXPIRED. Something is going wrong with the UNIX timestamp?');
  102. elif error == 'WRONG_EXPIRATION_TIME':
  103. raise EVOException("Problem contacting EVO; WRONG_EXPIRATION_TIME", 'REQUEST_EXPIRED. Something is going wrong with the UNIX timestamp?');
  104. else:
  105. raise EVOControlledException(error)
  106. else:
  107. raise EVOException('Error when contacting the EVO server for action: ' + action + '. Message from the EVO server did not start by ERROR or OK', answer)
  108. def parseEVOAnswer(answer):
  109. """ Parses an answer such as
  110. meet=48760&&start=0&&end=1000&&com=4&&type=0&&title=NewTestTitle&&desc=TestDesc&&url=[meeting=e9eIeivDveaeaBIDaaI9]
  111. and returns a tuple of attributes.
  112. the url attribute is transformed to the real koala URL
  113. """
  114. attributesStringList = answer.split('&&')
  115. attributes = {}
  116. for attributeString in attributesStringList:
  117. name, value = attributeString.split('=',1)
  118. name = name.strip()
  119. value = value.strip()
  120. if name == 'url':
  121. value = value[1:-1].strip() #we remove the brackets
  122. if value.startswith("meeting"): #the first part of the URL is not there
  123. value = getEVOOptionValueByName("koalaLocation") + '?' + value
  124. attributes[name] = value
  125. return attributes
  126. def createLoginKey(EVOID, password, time):
  127. """ Obfuscates an EVOID / password couple with a unix timestamp
  128. EVOID has to be an 8 digit (max) number / string number, ex: 123 or '123'
  129. password has to be a 4 digits password, ex: 1234 or '1234'
  130. time is unix time in milliseconds (13 digits max), ex: 12345
  131. Returns an "obfuscated" EVO login key of 25 characters.
  132. """
  133. EVOID = str(EVOID)
  134. password = str(password)
  135. time = str(time)
  136. if len(EVOID) > 8:
  137. raise EVOException("EVOID has to be 8 digits max")
  138. if len(password) != 4:
  139. raise EVOException("password has to be 4 digits")
  140. if len(time) > 13:
  141. raise EVOException("unix time has to be 13 digits max")
  142. key = array('c', ' '*25)
  143. EVOID = EVOID.zfill(8)
  144. time = time.zfill(13)
  145. for index, char in enumerate(time):
  146. key[index*2] = char
  147. for index, char in enumerate(EVOID):
  148. key[19 - index * 2] = char
  149. key[1] = password[0]
  150. key[21] = password[1]
  151. key[3] = password[2]
  152. key[23] = password[3]
  153. return key.tostring()
  154. def parseLoginKey(key):
  155. """ Parses an "obfuscated" EVO login key of 25 characters.
  156. Returns a tuple (EVOID, password, time)
  157. EVOID will be an string with a number of maximum 8 digits.
  158. time will be an integer, UNIX time in millesconds (13 digits max)
  159. password will be a string of 4 characters
  160. """
  161. if len(key) != 25:
  162. raise EVOException("key has to be a string of 25 characters")
  163. EVOID = str(int("".join([key[i] for i in range (19,3,-2)])))
  164. time = int("".join([key[i] for i in range(0,25,2)]))
  165. password = "".join([key[1],key[21], key[3], key[23]])
  166. return (EVOID, password, time)
  167. def getMinStartDate(conference):
  168. return conference.getAdjustedStartDate() - timedelta(0,0,0,0, getEVOOptionValueByName("extraMinutesBefore"))
  169. def getMaxEndDate(conference):
  170. return conference.getAdjustedEndDate() + timedelta(0,0,0,0, getEVOOptionValueByName("extraMinutesAfter"))
  171. class EVOError(CSErrorBase): #already Fossilizable
  172. fossilizes(IEVOErrorFossil)
  173. def __init__(self, errorType, requestURL = None, userMessage = None):
  174. CSErrorBase.__init__(self)
  175. self._errorType = errorType
  176. self._requestURL = requestURL
  177. self._userMessage = None
  178. def getErrorType(self):
  179. return self._errorType
  180. def getRequestURL(self):
  181. return self._requestURL
  182. def getUserMessage(self):
  183. if self._userMessage:
  184. return self._userMessage
  185. else:
  186. if self._errorType == 'duplicated':
  187. return "This EVO meeting could not be created or changed because EVO considers the resulting meeting as duplicated."
  188. elif self._errorType == 'start_in_past':
  189. return "This EVO meeting could not be created or changed because EVO does not allow meetings starting in the past."
  190. else:
  191. return self._errorType
  192. def getLogMessage(self):
  193. return "EVO Error: " + str(self._errorType) + " for request " + str(self._requestURL)
  194. class OverlappedError(EVOError): #already Fossilizable
  195. fossilizes(IOverlappedErrorFossil)
  196. def __init__(self, overlappedBooking):
  197. EVOError.__init__(self, 'overlapped')
  198. self._overlappedBooking = overlappedBooking
  199. def getSuperposedBooking(self):
  200. return self._overlappedBooking
  201. class ChangesFromEVOError(EVOError): #already Fossilizable
  202. fossilizes(IChangesFromEVOErrorFossil)
  203. def __init__(self, changes):
  204. EVOError.__init__(self, 'changesFromEVO')
  205. self._changes = changes
  206. def getChanges(self):
  207. return self._changes
  208. class EVOException(CollaborationException):
  209. def __init__(self, msg, inner = None):
  210. CollaborationException.__init__(self, msg, 'EVO', inner)
  211. class EVOControlledException(Exception):
  212. def __init__(self, message):
  213. self.message = message
  214. def __str__(self):
  215. return "EVOControlledException. Message = " + self.message
  216. class EVOWarning(Fossilizable):
  217. fossilizes(IEVOWarningFossil)
  218. def __init__(self, msg, exception = None):
  219. self._msg = msg
  220. self._exception = exception
  221. def getMessage(self):
  222. return self._msg
  223. def getException(self):
  224. return self._exception