diff options
author | Mike Crute <mcrute@gmail.com> | 2010-02-15 22:12:10 -0500 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2010-02-15 22:12:10 -0500 |
commit | a0013ea1f8a2dd374d3afd6349b89d7c7ab01bce (patch) | |
tree | c0d40706dea214746d7bed760ea9a136592d9810 | |
parent | df130cd5d132fc5eaf1c88b16c33289c9f31f559 (diff) | |
download | calendar_proxy-a0013ea1f8a2dd374d3afd6349b89d7c7ab01bce.tar.bz2 calendar_proxy-a0013ea1f8a2dd374d3afd6349b89d7c7ab01bce.tar.xz calendar_proxy-a0013ea1f8a2dd374d3afd6349b89d7c7ab01bce.zip |
Cleaning up authenticator and adding error checking.
-rw-r--r-- | exchange/authenticators.py | 121 |
1 files changed, 66 insertions, 55 deletions
diff --git a/exchange/authenticators.py b/exchange/authenticators.py index bcc2e38..4cc4c18 100644 --- a/exchange/authenticators.py +++ b/exchange/authenticators.py | |||
@@ -8,70 +8,81 @@ Exchange Server Authenticators | |||
8 | """ | 8 | """ |
9 | 9 | ||
10 | import urllib | 10 | import urllib |
11 | |||
12 | from copy import copy | ||
13 | from httplib import HTTPSConnection | ||
14 | from Cookie import SimpleCookie | 11 | from Cookie import SimpleCookie |
12 | from httplib import HTTPSConnection | ||
13 | from datetime import datetime, timedelta | ||
14 | from exchange import AuthenticationException | ||
15 | 15 | ||
16 | 16 | ||
17 | class ExchangeAuthenticator(object): | 17 | class CookieSession(object): |
18 | 18 | """ | |
19 | _auth_cookie = None | 19 | CookieSession implmenents the authentication protocol for the |
20 | authenticated = False | 20 | Exchange web interface. |
21 | username = None | 21 | """ |
22 | |||
23 | def __init__(self, web_server): | ||
24 | self.web_server = web_server | ||
25 | |||
26 | def authenticate(self, username, password): | ||
27 | """ | ||
28 | Authenticate the user and cache the authentication so we aren't | ||
29 | hammering the auth server. Should hanlde expiration eventually. | ||
30 | """ | ||
31 | self.username = username | ||
32 | |||
33 | if self._auth_cookie: | ||
34 | return self._auth_cookie | ||
35 | |||
36 | self._auth_cookie = self._do_authentication(username, password) | ||
37 | return self._auth_cookie | ||
38 | |||
39 | def _do_authentication(self, username, password): | ||
40 | raise NotImplemented | ||
41 | |||
42 | def patch_headers(self, headers): | ||
43 | raise NotImplemented | ||
44 | |||
45 | |||
46 | class CookieAuthenticator(ExchangeAuthenticator): | ||
47 | 22 | ||
48 | AUTH_DLL = "/exchweb/bin/auth/owaauth.dll" | 23 | AUTH_DLL = "/exchweb/bin/auth/owaauth.dll" |
49 | 24 | ||
50 | def _do_authentication(self, username, password): | 25 | def __init__(self, server, username=None, password=None): |
51 | """ | 26 | self.server = server |
52 | Does a post to the authentication DLL to fetch a cookie for the session | 27 | self.username = username |
53 | this can then be passed back to the exchange API for servers that don't | 28 | self.password = password |
54 | support basicc HTTP auth. | 29 | self.cache_timeout = timedelta(minutes=15) |
55 | """ | 30 | self._token = None |
56 | params = urllib.urlencode({ "destination": "https://%s/exchange" % (self.web_server), | 31 | self._last_modified = None |
57 | "flags": "0", | 32 | |
58 | "username": username, | 33 | @property |
59 | "password": password, | 34 | def has_expired(self): |
60 | "SubmitCreds": "Log On", | 35 | if not self._last_modified: |
61 | "trusted": "4" | 36 | return False |
62 | }) | 37 | |
63 | 38 | update_delta = datetime.now() - self._last_modified | |
64 | conn = HTTPSConnection(self.web_server) | 39 | return (update_delta >= self.cache_timeout) |
40 | |||
41 | @property | ||
42 | def is_authenticated(self): | ||
43 | return bool(self._token) and not self.has_expired | ||
44 | |||
45 | @property | ||
46 | def token(self): | ||
47 | if not self.is_authenticated: | ||
48 | self._authenticate() | ||
49 | self._check_auth() | ||
50 | |||
51 | return self._token | ||
52 | |||
53 | @token.setter | ||
54 | def token(self, token): | ||
55 | self._last_modified = datetime.now() | ||
56 | self._token = token.strip() | ||
57 | |||
58 | def _authenticate(self): | ||
59 | # Another idiotic Exchange issue, you MUST pass the redirect | ||
60 | # destination, and what's more if you don't pass it the name | ||
61 | # of the current server + /echange it will fail to auth | ||
62 | |||
63 | params = urllib.urlencode({ | ||
64 | "destination": "https://{0}/exchange".format(self.server), | ||
65 | "username": self.username, | ||
66 | "password": self.password, | ||
67 | }) | ||
68 | |||
69 | conn = HTTPSConnection(self.server) | ||
65 | conn.request("POST", self.AUTH_DLL, params) | 70 | conn.request("POST", self.AUTH_DLL, params) |
66 | response = conn.getresponse() | 71 | response = conn.getresponse() |
67 | 72 | ||
68 | cookie = SimpleCookie(response.getheader("set-cookie")) | 73 | cookie = SimpleCookie(response.getheader("set-cookie")) |
69 | cookie = ("sessionid=%s" % cookie["sessionid"].value, "cadata=%s" % cookie["cadata"].value) | 74 | self.token = cookie.output(attrs=[], header='', sep=';') |
75 | |||
76 | def _check_auth(self): | ||
77 | # Grrr... Exchange is idiotic, instead of returning the correct error | ||
78 | # code with the auth they force you to follow a redirect. If you | ||
79 | # don't get a 200 (you'll get a 302) then you can rest assured that | ||
80 | # your authentication failed. | ||
70 | 81 | ||
71 | self.authenticated = True | 82 | conn = HTTPSConnection(self.server) |
72 | return "; ".join(cookie) | 83 | conn.request("GET", '/exchange/', |
84 | headers=dict(Cookie=self.token)) | ||
85 | resp = conn.getresponse() | ||
73 | 86 | ||
74 | def patch_headers(self, headers): | 87 | if not resp.status == 200: |
75 | out_headers = copy(headers) | 88 | raise AuthenticationException |
76 | out_headers["Cookie"] = self._auth_cookie | ||
77 | return out_headers | ||