diff options
Diffstat (limited to 'milkman')
-rw-r--r-- | milkman/__init__.py | 11 | ||||
-rw-r--r-- | milkman/auth.py | 170 | ||||
-rw-r--r-- | milkman/tests/test_auth.py | 16 |
3 files changed, 197 insertions, 0 deletions
diff --git a/milkman/__init__.py b/milkman/__init__.py new file mode 100644 index 0000000..727ff90 --- /dev/null +++ b/milkman/__init__.py | |||
@@ -0,0 +1,11 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Milkman - A Remember The Milk API | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: February 03, 2010 | ||
8 | """ | ||
9 | |||
10 | |||
11 | API_URL = "http://api.rememberthemilk.com/services" | ||
diff --git a/milkman/auth.py b/milkman/auth.py new file mode 100644 index 0000000..0dfb36d --- /dev/null +++ b/milkman/auth.py | |||
@@ -0,0 +1,170 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Remember The Milk Authentication | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: February 03, 2010 | ||
8 | """ | ||
9 | |||
10 | import json | ||
11 | import urllib | ||
12 | from logging import getLogger | ||
13 | from hashlib import md5 | ||
14 | from milkman import API_URL | ||
15 | |||
16 | |||
17 | logger = getLogger('rtmapi') | ||
18 | |||
19 | |||
20 | class APIPerms(object): | ||
21 | |||
22 | READ = "read" | ||
23 | WRITE = "write" | ||
24 | DELETE = "delete" | ||
25 | |||
26 | |||
27 | def flatten_sorted_dict(in_dict): | ||
28 | items = sorted(in_dict.items()) | ||
29 | flat_items = [''.join(item) for item in items] | ||
30 | return ''.join(flat_items) | ||
31 | |||
32 | |||
33 | class APIError(Exception): | ||
34 | pass | ||
35 | |||
36 | |||
37 | class Request(object): | ||
38 | |||
39 | def __init__(self, api_key, secret, service='rest'): | ||
40 | self.service = service | ||
41 | self.secret = secret | ||
42 | self.params = { | ||
43 | 'format': 'json', | ||
44 | 'api_key': api_key, | ||
45 | } | ||
46 | |||
47 | @classmethod | ||
48 | def get_factory(cls, api_key, secret): | ||
49 | return lambda: cls(api_key, secret) | ||
50 | |||
51 | @property | ||
52 | def url(self): | ||
53 | self._sign_request() | ||
54 | params = urllib.urlencode(self.params) | ||
55 | return '{0}/{1}?{2}'.format(API_URL, self.service, params) | ||
56 | |||
57 | def _sign_request(self): | ||
58 | param_string = self.secret | ||
59 | param_string += flatten_sorted_dict(self.params) | ||
60 | self.params['api_sig'] = md5(param_string).hexdigest() | ||
61 | |||
62 | def __setitem__(self, key, value): | ||
63 | self.params[key] = value | ||
64 | |||
65 | def __getitem__(self, key): | ||
66 | return self.params[key] | ||
67 | |||
68 | def _parse_json_data(self, data): | ||
69 | body = data['rsp'] | ||
70 | status = body['stat'] | ||
71 | |||
72 | if status == 'fail': | ||
73 | error = APIError(body['err']['msg']) | ||
74 | error.code = int(body['err']['code']) | ||
75 | raise error | ||
76 | |||
77 | del body['stat'] | ||
78 | |||
79 | return body | ||
80 | |||
81 | def execute(self): | ||
82 | data = urllib.urlopen(self.url) | ||
83 | raw_data = data.read() | ||
84 | json_data = json.loads(raw_data) | ||
85 | |||
86 | return self._parse_json_data(json_data) | ||
87 | |||
88 | |||
89 | class Authenticator(object): | ||
90 | |||
91 | def __init__(self, request_factory): | ||
92 | self.request_factory = request_factory | ||
93 | self.token = None | ||
94 | |||
95 | def authenticate(self, token=None): | ||
96 | if token: | ||
97 | self.token = token | ||
98 | |||
99 | if not self.check_token(self.token): | ||
100 | raw_input("Please visit: {0}".format(self.get_auth_url())) | ||
101 | self.token = self.get_token() | ||
102 | |||
103 | def get_auth_url(self): | ||
104 | req = self.request_factory() | ||
105 | req.service = 'auth' | ||
106 | req['perms'] = APIPerms.DELETE | ||
107 | req['frob'] = self._get_frob() | ||
108 | |||
109 | return req.url | ||
110 | |||
111 | def _get_frob(self): | ||
112 | req = self.request_factory() | ||
113 | req['method'] = 'rtm.auth.getFrob' | ||
114 | self.frob = req.execute()['frob'] | ||
115 | |||
116 | return self.frob | ||
117 | |||
118 | def get_token(self): | ||
119 | req = self.request_factory() | ||
120 | req['method'] = 'rtm.auth.getToken' | ||
121 | req['frob'] = self.frob | ||
122 | |||
123 | print req.execute() | ||
124 | |||
125 | def check_token(self, token): | ||
126 | if not token: | ||
127 | return False | ||
128 | |||
129 | req = self.request_factory() | ||
130 | req['method'] = 'rtm.auth.checkToken' | ||
131 | req['token'] = token | ||
132 | |||
133 | try: | ||
134 | req.execute() | ||
135 | return True | ||
136 | except APIError, err: | ||
137 | if err.code == 98: | ||
138 | return False | ||
139 | raise | ||
140 | |||
141 | |||
142 | class APIConnection(object): | ||
143 | |||
144 | def __init__(self, api_key, secret): | ||
145 | self.request_factory = Request.get_factory(api_key, secret) | ||
146 | self.auth = Authenticator(self.request_factory) | ||
147 | |||
148 | def authenticate(self, token=None): | ||
149 | self.auth.authenticate(token) | ||
150 | print self.auth.token | ||
151 | |||
152 | def get_lists(self): | ||
153 | req = self.request_factory() | ||
154 | req['method'] = 'rtm.lists.getList' | ||
155 | req['auth_token'] = self.auth.token | ||
156 | |||
157 | print req.execute() | ||
158 | |||
159 | |||
160 | |||
161 | if __name__ == '__main__': | ||
162 | API_KEY = "38816a01a7937588081d33ea74b6f5ee" | ||
163 | SECRET = "d4caed275cf9a63e" | ||
164 | |||
165 | TOKEN = "54f3378cb114e6cc330e1c2fd34a11e96e2a26c5" | ||
166 | |||
167 | api = APIConnection(API_KEY, SECRET) | ||
168 | api.authenticate(TOKEN) | ||
169 | api.get_lists() | ||
170 | |||
diff --git a/milkman/tests/test_auth.py b/milkman/tests/test_auth.py new file mode 100644 index 0000000..8a4e7d2 --- /dev/null +++ b/milkman/tests/test_auth.py | |||
@@ -0,0 +1,16 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Authentication Test Suite | ||
4 | |||
5 | @author: Mike Crute (mcrute@ag.com) | ||
6 | @organization: American Greetings Interactive | ||
7 | @date: February 03, 2010 | ||
8 | """ | ||
9 | |||
10 | from milkman.auth import flatten_sorted_dict, md5_sign | ||
11 | |||
12 | |||
13 | def test_sort_dict(): | ||
14 | input = { 'alpha': '1', 'ahla': '2', 'beta': '3' } | ||
15 | output = flatten_sorted_dict(input) | ||
16 | assert output == 'ahla2alpha1beta3' | ||