summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2009-04-26 12:33:56 -0400
committerMike Crute <mcrute@gmail.com>2009-04-26 12:33:56 -0400
commit1d7b31d19562823dda93b045b786606191fd3e5b (patch)
treef937b3b72b4ab37553764da36e85febbadbaed3e
parent8bf001babf419da9e830c00a93d8b0953a71c9e6 (diff)
downloadcalendar_proxy-1d7b31d19562823dda93b045b786606191fd3e5b.tar.bz2
calendar_proxy-1d7b31d19562823dda93b045b786606191fd3e5b.tar.xz
calendar_proxy-1d7b31d19562823dda93b045b786606191fd3e5b.zip
Housekeeping. No code changes.
-rw-r--r--exchange.py116
1 files changed, 58 insertions, 58 deletions
diff --git a/exchange.py b/exchange.py
index 42ba547..38ee312 100644
--- a/exchange.py
+++ b/exchange.py
@@ -6,7 +6,7 @@ Exchange API -> iCal Fie Proxy
6@version: $Revision$ 6@version: $Revision$
7 7
8This is a set of classes that starts to define a set of classes for 8This is a set of classes that starts to define a set of classes for
9fetching data using Exchange's WebDAV API. This is still pretty 9fetching data using Exchange's WebDAV API. This is still pretty
10development code but it does the trick. Watch out, it doesn't consider 10development code but it does the trick. Watch out, it doesn't consider
11many corner cases. 11many corner cases.
12 12
@@ -61,19 +61,19 @@ class CalendarInstanceType(object):
61 """ 61 """
62 Enum for Calendar Instance Types 62 Enum for Calendar Instance Types
63 @see: http://msdn.microsoft.com/en-us/library/ms870457(EXCHG.65).aspx 63 @see: http://msdn.microsoft.com/en-us/library/ms870457(EXCHG.65).aspx
64 64
65 This ended up not being used but its probably good to keep around for 65 This ended up not being used but its probably good to keep around for
66 future use. 66 future use.
67 """ 67 """
68 #: Single appointment 68 #: Single appointment
69 SINGLE = 0 69 SINGLE = 0
70 70
71 #: Master recurring appointment 71 #: Master recurring appointment
72 MASTER = 1 72 MASTER = 1
73 73
74 #: Single instance of a recurring appointment 74 #: Single instance of a recurring appointment
75 INSTANCE = 2 75 INSTANCE = 2
76 76
77 #: Exception to a recurring appointment 77 #: Exception to a recurring appointment
78 EXCEPTION = 3 78 EXCEPTION = 3
79 79
@@ -101,61 +101,61 @@ class ExchangeAuthenticator(object):
101 _auth_cookie = None 101 _auth_cookie = None
102 authenticated = False 102 authenticated = False
103 username = None 103 username = None
104 104
105 def __init__(self, web_server): 105 def __init__(self, web_server):
106 self.web_server = web_server 106 self.web_server = web_server
107 107
108 def authenticate(self, username, password): 108 def authenticate(self, username, password):
109 """ 109 """
110 Authenticate the user and cache the authentication so we aren't 110 Authenticate the user and cache the authentication so we aren't
111 hammering the auth server. Should hanlde expiration eventually. 111 hammering the auth server. Should hanlde expiration eventually.
112 """ 112 """
113 self.username = username 113 self.username = username
114 114
115 if self._auth_cookie: 115 if self._auth_cookie:
116 return self._auth_cookie 116 return self._auth_cookie
117 117
118 self._auth_cookie = self._do_authentication(username, password) 118 self._auth_cookie = self._do_authentication(username, password)
119 return self._auth_cookie 119 return self._auth_cookie
120 120
121 def _do_authentication(self, username, password): 121 def _do_authentication(self, username, password):
122 raise NotImplemented("Implement in a subclass.") 122 raise NotImplemented("Implement in a subclass.")
123 123
124 def patch_headers(self, headers): 124 def patch_headers(self, headers):
125 raise NotImplemented("Implement in a subclass.") 125 raise NotImplemented("Implement in a subclass.")
126 126
127class CookieAuthenticator(ExchangeAuthenticator): 127class CookieAuthenticator(ExchangeAuthenticator):
128 #: Authentication DLL on the exchange server 128 #: Authentication DLL on the exchange server
129 AUTH_DLL = "/exchweb/bin/auth/owaauth.dll" 129 AUTH_DLL = "/exchweb/bin/auth/owaauth.dll"
130 130
131 def _do_authentication(self, username, password): 131 def _do_authentication(self, username, password):
132 """ 132 """
133 Does a post to the authentication DLL to fetch a cookie for the session 133 Does a post to the authentication DLL to fetch a cookie for the session
134 this can then be passed back to the exchange API for servers that don't 134 this can then be passed back to the exchange API for servers that don't
135 support basicc HTTP auth. 135 support basicc HTTP auth.
136 """ 136 """
137 params = urllib.urlencode({ "destination": "https://%s/exchange" % (self.web_server), 137 params = urllib.urlencode({ "destination": "https://%s/exchange" % (self.web_server),
138 "flags": "0", 138 "flags": "0",
139 "username": username, 139 "username": username,
140 "password": password, 140 "password": password,
141 "SubmitCreds": "Log On", 141 "SubmitCreds": "Log On",
142 "trusted": "4" 142 "trusted": "4"
143 }) 143 })
144 144
145 conn = HTTPSConnection(self.web_server) 145 conn = HTTPSConnection(self.web_server)
146 conn.request("POST", self.AUTH_DLL, params) 146 conn.request("POST", self.AUTH_DLL, params)
147 response = conn.getresponse() 147 response = conn.getresponse()
148 148
149 cookie = SimpleCookie(response.getheader("set-cookie")) 149 cookie = SimpleCookie(response.getheader("set-cookie"))
150 cookie = ("sessionid=%s" % cookie["sessionid"].value, "cadata=%s" % cookie["cadata"].value) 150 cookie = ("sessionid=%s" % cookie["sessionid"].value, "cadata=%s" % cookie["cadata"].value)
151 151
152 self.authenticated = True 152 self.authenticated = True
153 return "; ".join(cookie) 153 return "; ".join(cookie)
154 154
155 def patch_headers(self, headers): 155 def patch_headers(self, headers):
156 """ 156 """
157 Patch the headers dictionary with authentication information and 157 Patch the headers dictionary with authentication information and
158 return the patched dictionary. I'm not a big fan of patching 158 return the patched dictionary. I'm not a big fan of patching
159 dictionaries in-place so just make a copy first. 159 dictionaries in-place so just make a copy first.
160 """ 160 """
161 out_headers = copy(headers) 161 out_headers = copy(headers)
@@ -170,21 +170,21 @@ class ExchangeCommand(object):
170 Base class for Exchange commands. This really shouldn't be constructed 170 Base class for Exchange commands. This really shouldn't be constructed
171 directly but should be subclassed to do useful things. 171 directly but should be subclassed to do useful things.
172 """ 172 """
173 173
174 #: Base URL for Exchange commands. 174 #: Base URL for Exchange commands.
175 BASE_URL = Template("/exchange/${username}/${method}") 175 BASE_URL = Template("/exchange/${username}/${method}")
176 176
177 #: Basic headers that are required for all requests 177 #: Basic headers that are required for all requests
178 BASE_HEADERS = { 178 BASE_HEADERS = {
179 "Content-Type": 'text/xml; charset="UTF-8"', 179 "Content-Type": 'text/xml; charset="UTF-8"',
180 "Depth": "0", 180 "Depth": "0",
181 "Translate": "f", 181 "Translate": "f",
182 } 182 }
183 183
184 def __init__(self, server, authenticator=None): 184 def __init__(self, server, authenticator=None):
185 self.server = server 185 self.server = server
186 self.authenticator = authenticator 186 self.authenticator = authenticator
187 187
188 def _get_xml(self, **kwargs): 188 def _get_xml(self, **kwargs):
189 """ 189 """
190 Try to get an XML response from the server. 190 Try to get an XML response from the server.
@@ -192,16 +192,16 @@ class ExchangeCommand(object):
192 """ 192 """
193 if not self.authenticator.authenticated: 193 if not self.authenticator.authenticated:
194 raise ExchangeException("Not authenticated. Call authenticate() first.") 194 raise ExchangeException("Not authenticated. Call authenticate() first.")
195 195
196 # Lets forcibly override the username with the user we're querying as 196 # Lets forcibly override the username with the user we're querying as
197 kwargs["username"] = self.authenticator.username 197 kwargs["username"] = self.authenticator.username
198 198
199 xml = self._get_query(**kwargs) 199 xml = self._get_query(**kwargs)
200 url = self.BASE_URL.substitute({ "username": self.authenticator.username, 200 url = self.BASE_URL.substitute({ "username": self.authenticator.username,
201 "method": self.exchange_method }) 201 "method": self.exchange_method })
202 query = Template(xml).substitute(kwargs) 202 query = Template(xml).substitute(kwargs)
203 send_headers = self.authenticator.patch_headers(self.BASE_HEADERS) 203 send_headers = self.authenticator.patch_headers(self.BASE_HEADERS)
204 204
205 conn = HTTPSConnection(self.server) 205 conn = HTTPSConnection(self.server)
206 conn.request(self.dav_method.upper(), url, query, headers=send_headers) 206 conn.request(self.dav_method.upper(), url, query, headers=send_headers)
207 resp = conn.getresponse() 207 resp = conn.getresponse()
@@ -211,22 +211,22 @@ class ExchangeCommand(object):
211 raise ExchangeException("%s %s" % (resp.status, resp.reason)) 211 raise ExchangeException("%s %s" % (resp.status, resp.reason))
212 212
213 return etree.fromstring(resp.read()) 213 return etree.fromstring(resp.read())
214 214
215 def _get_query(self, **kwargs): 215 def _get_query(self, **kwargs):
216 """ 216 """
217 Build up the XML query for the server. Mostly just does a lot 217 Build up the XML query for the server. Mostly just does a lot
218 of template substitutions, also does a little bit of elementtree 218 of template substitutions, also does a little bit of elementtree
219 magic to to build the XML query. 219 magic to to build the XML query.
220 """ 220 """
221 declaration = etree.ProcessingInstruction("xml", 'version="1.0"') 221 declaration = etree.ProcessingInstruction("xml", 'version="1.0"')
222 222
223 request = etree.Element("g:searchrequest", { "xmlns:g": "DAV:" }) 223 request = etree.Element("g:searchrequest", { "xmlns:g": "DAV:" })
224 query = etree.SubElement(request, "g:sql") 224 query = etree.SubElement(request, "g:sql")
225 query.text = Template(self.sql).substitute(kwargs) 225 query.text = Template(self.sql).substitute(kwargs)
226 226
227 output = etree.tostring(declaration) 227 output = etree.tostring(declaration)
228 output += etree.tostring(request) 228 output += etree.tostring(request)
229 229
230 return output 230 return output
231 231
232 232
@@ -236,34 +236,34 @@ class ExchangeCommand(object):
236class FetchCalendar(ExchangeCommand): 236class FetchCalendar(ExchangeCommand):
237 exchange_method = "calendar" 237 exchange_method = "calendar"
238 dav_method = "search" 238 dav_method = "search"
239 239
240 sql = """ 240 sql = """
241 SELECT 241 SELECT
242 "urn:schemas:calendar:location" AS location, 242 "urn:schemas:calendar:location" AS location,
243 "urn:schemas:httpmail:normalizedsubject" AS subject, 243 "urn:schemas:httpmail:normalizedsubject" AS subject,
244 "urn:schemas:calendar:dtstart" AS start_date, 244 "urn:schemas:calendar:dtstart" AS start_date,
245 "urn:schemas:calendar:dtend" AS end_date, 245 "urn:schemas:calendar:dtend" AS end_date,
246 "urn:schemas:calendar:busystatus" AS busy_status, 246 "urn:schemas:calendar:busystatus" AS busy_status,
247 "urn:schemas:calendar:instancetype" AS instance_type, 247 "urn:schemas:calendar:instancetype" AS instance_type,
248 "urn:schemas:calendar:timezone" AS timezone_info, 248 "urn:schemas:calendar:timezone" AS timezone_info,
249 "urn:schemas:httpmail:textdescription" AS description 249 "urn:schemas:httpmail:textdescription" AS description
250 FROM 250 FROM
251 Scope('SHALLOW TRAVERSAL OF "/exchange/${username}/calendar/"') 251 Scope('SHALLOW TRAVERSAL OF "/exchange/${username}/calendar/"')
252 WHERE 252 WHERE
253 NOT "urn:schemas:calendar:instancetype" = 1 253 NOT "urn:schemas:calendar:instancetype" = 1
254 AND "DAV:contentclass" = 'urn:content-classes:appointment' 254 AND "DAV:contentclass" = 'urn:content-classes:appointment'
255 ORDER BY 255 ORDER BY
256 "urn:schemas:calendar:dtstart" ASC 256 "urn:schemas:calendar:dtstart" ASC
257 """ 257 """
258 258
259 def execute(self, alarms=True, alarm_offset=15, **kwargs): 259 def execute(self, alarms=True, alarm_offset=15, **kwargs):
260 exchange_xml = self._get_xml(**kwargs) 260 exchange_xml = self._get_xml(**kwargs)
261 calendar = Calendar() 261 calendar = Calendar()
262 262
263 for item in exchange_xml.getchildren(): 263 for item in exchange_xml.getchildren():
264 item = item.find("{DAV:}propstat").find("{DAV:}prop") 264 item = item.find("{DAV:}propstat").find("{DAV:}prop")
265 event = Event() 265 event = Event()
266 266
267 # These tests may look funny but the result of item.find 267 # These tests may look funny but the result of item.find
268 # does NOT evaluate to true even though it is not None 268 # does NOT evaluate to true even though it is not None
269 # so, we have to check the interface of the returned item 269 # so, we have to check the interface of the returned item
@@ -276,15 +276,15 @@ class FetchCalendar(ExchangeCommand):
276 location = item.find("location") 276 location = item.find("location")
277 if hasattr(location, "text"): 277 if hasattr(location, "text"):
278 event.add("location", location.text) 278 event.add("location", location.text)
279 279
280 description = item.find("description") 280 description = item.find("description")
281 if hasattr(description, "text"): 281 if hasattr(description, "text"):
282 event.add("description", description.text) 282 event.add("description", description.text)
283 283
284 # Dates should always exist 284 # Dates should always exist
285 start_date = dateutil.parser.parse(item.find("start_date").text) 285 start_date = dateutil.parser.parse(item.find("start_date").text)
286 event.add("dtstart", start_date) 286 event.add("dtstart", start_date)
287 287
288 end_date = dateutil.parser.parse(item.find("end_date").text) 288 end_date = dateutil.parser.parse(item.find("end_date").text)
289 event.add("dtend", end_date) 289 event.add("dtend", end_date)
290 290
@@ -303,7 +303,7 @@ class FetchCalendar(ExchangeCommand):
303 event.add_component(alarm) 303 event.add_component(alarm)
304 304
305 calendar.add_component(event) 305 calendar.add_component(event)
306 306
307 return calendar 307 return calendar
308 308
309 309
@@ -314,10 +314,10 @@ if __name__ == "__main__":
314 import sys 314 import sys
315 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 315 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
316 from ConfigParser import ConfigParser 316 from ConfigParser import ConfigParser
317 317
318 config = ConfigParser() 318 config = ConfigParser()
319 config.read("exchange.cfg") 319 config.read("exchange.cfg")
320 320
321 username = config.get("exchange", "user") 321 username = config.get("exchange", "user")
322 password = getpass("Exchange Password: ") 322 password = getpass("Exchange Password: ")
323 323
@@ -327,22 +327,22 @@ if __name__ == "__main__":
327 327
328 server = config.get("exchange", "server") 328 server = config.get("exchange", "server")
329 fetcher = FetchCalendar(server) 329 fetcher = FetchCalendar(server)
330 330
331 authenticator = CookieAuthenticator(server) 331 authenticator = CookieAuthenticator(server)
332 authenticator.authenticate(username, password) 332 authenticator.authenticate(username, password)
333 fetcher.authenticator = authenticator 333 fetcher.authenticator = authenticator
334 334
335 calendar = fetcher.execute() 335 calendar = fetcher.execute()
336 336
337 self.wfile.write(calendar.as_string()) 337 self.wfile.write(calendar.as_string())
338 self.wfile.close() 338 self.wfile.close()
339 339
340 try: 340 try:
341 bind_address = config.get("local_server", "address") 341 bind_address = config.get("local_server", "address")
342 bind_port = int(config.get("local_server", "port")) 342 bind_port = int(config.get("local_server", "port"))
343 343
344 print "Exchange iCal Proxy Running on port %d" % bind_port 344 print "Exchange iCal Proxy Running on port %d" % bind_port
345 345
346 server = HTTPServer((bind_address, bind_port), CalendarHandler) 346 server = HTTPServer((bind_address, bind_port), CalendarHandler)
347 server.serve_forever() 347 server.serve_forever()
348 except KeyboardInterrupt: 348 except KeyboardInterrupt: