Status: | Merged |
---|---|
Approved by: | Soren Hansen |
Approved revision: | 21 |
Merged at revision: | 21 |
Proposed branch: | lp:~soren/surveilr/auth |
Merge into: | lp:surveilr |
Diff against target: |
643 lines (+387/-22) 15 files modified
surveilr/api/__init__.py (+19/-0) surveilr/api/server/__init__.py (+21/-0) surveilr/api/server/app.py (+42/-10) surveilr/api/server/auth.py (+48/-0) surveilr/api/server/factory.py (+29/-0) surveilr/defaults.cfg (+12/-3) surveilr/models.py (+6/-0) surveilr/tests/api/server/__init__.py (+19/-0) surveilr/tests/api/server/test_app.py (+30/-9) surveilr/tests/api/server/test_auth.py (+82/-0) surveilr/tests/api/server/test_factory.py (+43/-0) surveilr/tests/test_utils.py (+6/-0) surveilr/utils.py (+6/-0) surveilr/who.ini (+23/-0) tools/pip-requirements.txt (+1/-0) |
To merge this branch: | bzr merge lp:~soren/surveilr/auth |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Soren Hansen | Pending | ||
Review via email: mp+87544@code.launchpad.net |
Commit message
Add authentication
Use repoze.who to provide a simple authentication system.
Description of the change
To post a comment you must log in.
lp:~soren/surveilr/auth
updated
- 21. By Soren Hansen
-
Add authentication
Use repoze.who to provide a simple authentication system.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'surveilr/api/__init__.py' | |||
2 | --- surveilr/api/__init__.py 2011-11-19 22:01:55 +0000 | |||
3 | +++ surveilr/api/__init__.py 2012-01-04 23:13:32 +0000 | |||
4 | @@ -0,0 +1,19 @@ | |||
5 | 1 | """ | ||
6 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
7 | 3 | |||
8 | 4 | Copyright (C) 2011 Linux2Go | ||
9 | 5 | |||
10 | 6 | This program is free software: you can redistribute it and/or | ||
11 | 7 | modify it under the terms of the GNU Affero General Public License | ||
12 | 8 | as published by the Free Software Foundation, either version 3 of | ||
13 | 9 | the License, or (at your option) any later version. | ||
14 | 10 | |||
15 | 11 | This program is distributed in the hope that it will be useful, | ||
16 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | 14 | GNU Affero General Public License for more details. | ||
19 | 15 | |||
20 | 16 | You should have received a copy of the GNU Affero General Public | ||
21 | 17 | License along with this program. If not, see | ||
22 | 18 | <http://www.gnu.org/licenses/>. | ||
23 | 19 | """ | ||
24 | 0 | 20 | ||
25 | === added directory 'surveilr/api/server' | |||
26 | === added file 'surveilr/api/server/__init__.py' | |||
27 | --- surveilr/api/server/__init__.py 1970-01-01 00:00:00 +0000 | |||
28 | +++ surveilr/api/server/__init__.py 2012-01-04 23:13:32 +0000 | |||
29 | @@ -0,0 +1,21 @@ | |||
30 | 1 | """ | ||
31 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
32 | 3 | |||
33 | 4 | Copyright (C) 2011 Linux2Go | ||
34 | 5 | |||
35 | 6 | This program is free software: you can redistribute it and/or | ||
36 | 7 | modify it under the terms of the GNU Affero General Public License | ||
37 | 8 | as published by the Free Software Foundation, either version 3 of | ||
38 | 9 | the License, or (at your option) any later version. | ||
39 | 10 | |||
40 | 11 | This program is distributed in the hope that it will be useful, | ||
41 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
42 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
43 | 14 | GNU Affero General Public License for more details. | ||
44 | 15 | |||
45 | 16 | You should have received a copy of the GNU Affero General Public | ||
46 | 17 | License along with this program. If not, see | ||
47 | 18 | <http://www.gnu.org/licenses/>. | ||
48 | 19 | |||
49 | 20 | Log collection server implementation | ||
50 | 21 | """ | ||
51 | 0 | 22 | ||
52 | === renamed file 'surveilr/api/server.py' => 'surveilr/api/server/app.py' | |||
53 | --- surveilr/api/server.py 2011-12-18 14:46:48 +0000 | |||
54 | +++ surveilr/api/server/app.py 2012-01-04 23:13:32 +0000 | |||
55 | @@ -18,11 +18,12 @@ | |||
56 | 18 | License along with this program. If not, see | 18 | License along with this program. If not, see |
57 | 19 | <http://www.gnu.org/licenses/>. | 19 | <http://www.gnu.org/licenses/>. |
58 | 20 | 20 | ||
60 | 21 | Log collection server implementation | 21 | API server implementation |
61 | 22 | """ | 22 | """ |
62 | 23 | 23 | ||
63 | 24 | import eventlet | 24 | import eventlet |
64 | 25 | import eventlet.wsgi | 25 | import eventlet.wsgi |
65 | 26 | import functools | ||
66 | 26 | import json | 27 | import json |
67 | 27 | import time | 28 | import time |
68 | 28 | 29 | ||
69 | @@ -34,12 +35,29 @@ | |||
70 | 34 | from webob import Response | 35 | from webob import Response |
71 | 35 | from webob.dec import wsgify | 36 | from webob.dec import wsgify |
72 | 36 | from webob.exc import HTTPNotFound | 37 | from webob.exc import HTTPNotFound |
73 | 38 | from webob.exc import HTTPForbidden | ||
74 | 37 | 39 | ||
75 | 38 | from surveilr import config | 40 | from surveilr import config |
76 | 39 | from surveilr import messaging | 41 | from surveilr import messaging |
77 | 40 | from surveilr import models | 42 | from surveilr import models |
78 | 41 | from surveilr import utils | 43 | from surveilr import utils |
79 | 42 | 44 | ||
80 | 45 | def is_privileged(req): | ||
81 | 46 | if 'surveilr.user' in req.environ: | ||
82 | 47 | return req.environ['surveilr.user'].credentials['admin'] | ||
83 | 48 | # This feels pretty scary | ||
84 | 49 | return True | ||
85 | 50 | |||
86 | 51 | |||
87 | 52 | def privileged(f): | ||
88 | 53 | @functools.wraps(f) | ||
89 | 54 | def wrapped(self, req, *args): | ||
90 | 55 | if is_privileged(req): | ||
91 | 56 | return f(self, req, *args) | ||
92 | 57 | else: | ||
93 | 58 | return HTTPForbidden() | ||
94 | 59 | return wrapped | ||
95 | 60 | |||
96 | 43 | 61 | ||
97 | 44 | class NotificationController(object): | 62 | class NotificationController(object): |
98 | 45 | """Routes style controller for notifications""" | 63 | """Routes style controller for notifications""" |
99 | @@ -58,15 +76,30 @@ | |||
100 | 58 | class UserController(object): | 76 | class UserController(object): |
101 | 59 | """Routes style controller for actions related to users""" | 77 | """Routes style controller for actions related to users""" |
102 | 60 | 78 | ||
103 | 79 | @privileged | ||
104 | 61 | def create(self, req): | 80 | def create(self, req): |
105 | 62 | """Called for POST requests to /users | 81 | """Called for POST requests to /users |
106 | 63 | 82 | ||
107 | 64 | Creates the user, returns a JSON object with the ID assigned | 83 | Creates the user, returns a JSON object with the ID assigned |
108 | 65 | to the user""" | 84 | to the user""" |
109 | 66 | data = json.loads(req.body) | 85 | data = json.loads(req.body) |
111 | 67 | user = models.User(**data) | 86 | |
112 | 87 | obj_data = {} | ||
113 | 88 | |||
114 | 89 | obj_data['credentials'] = {} | ||
115 | 90 | |||
116 | 91 | if 'admin' in data: | ||
117 | 92 | obj_data['credentials']['admin'] = data['admin'] | ||
118 | 93 | |||
119 | 94 | for key in ['messaging_driver', 'messaging_address']: | ||
120 | 95 | if key in data: | ||
121 | 96 | obj_data[key] = data[key] | ||
122 | 97 | |||
123 | 98 | user = models.User(**obj_data) | ||
124 | 68 | user.save() | 99 | user.save() |
126 | 69 | response = {'id': user.key} | 100 | response = {'id': user.key, |
127 | 101 | 'key': user.api_key, | ||
128 | 102 | 'admin': user.credentials.get('admin', False)} | ||
129 | 70 | return Response(json.dumps(response)) | 103 | return Response(json.dumps(response)) |
130 | 71 | 104 | ||
131 | 72 | def show(self, req, id): | 105 | def show(self, req, id): |
132 | @@ -77,7 +110,8 @@ | |||
133 | 77 | user = models.User.get(id) | 110 | user = models.User.get(id) |
134 | 78 | resp_dict = {'id': user.key, | 111 | resp_dict = {'id': user.key, |
135 | 79 | 'messaging_driver': user.messaging_driver, | 112 | 'messaging_driver': user.messaging_driver, |
137 | 80 | 'messaging_address': user.messaging_address} | 113 | 'messaging_address': user.messaging_address, |
138 | 114 | 'admin': user.credentials.get('admin', False)} | ||
139 | 81 | return Response(json.dumps(resp_dict)) | 115 | return Response(json.dumps(resp_dict)) |
140 | 82 | except riakalchemy.NoSuchObjectError: | 116 | except riakalchemy.NoSuchObjectError: |
141 | 83 | return HTTPNotFound() | 117 | return HTTPNotFound() |
142 | @@ -183,7 +217,10 @@ | |||
143 | 183 | path_prefix='/users/{user_id}') | 217 | path_prefix='/users/{user_id}') |
144 | 184 | 218 | ||
145 | 185 | def __init__(self, global_config): | 219 | def __init__(self, global_config): |
147 | 186 | pass | 220 | riak_host = config.get_str('riak', 'host') |
148 | 221 | riak_port = config.get_int('riak', 'port') | ||
149 | 222 | |||
150 | 223 | riakalchemy.connect(host=riak_host, port=riak_port) | ||
151 | 187 | 224 | ||
152 | 188 | @wsgify | 225 | @wsgify |
153 | 189 | def __call__(self, req): | 226 | def __call__(self, req): |
154 | @@ -216,11 +253,6 @@ | |||
155 | 216 | 253 | ||
156 | 217 | 254 | ||
157 | 218 | def main(): | 255 | def main(): |
158 | 219 | riak_host = config.get_str('riak', 'host') | ||
159 | 220 | riak_port = config.get_int('riak', 'port') | ||
160 | 221 | |||
161 | 222 | riakalchemy.connect(host=riak_host, port=riak_port) | ||
162 | 223 | |||
163 | 224 | server_factory({}, '', 9877)(SurveilrApplication({})) | 256 | server_factory({}, '', 9877)(SurveilrApplication({})) |
164 | 225 | 257 | ||
165 | 226 | 258 | ||
166 | 227 | 259 | ||
167 | === added file 'surveilr/api/server/auth.py' | |||
168 | --- surveilr/api/server/auth.py 1970-01-01 00:00:00 +0000 | |||
169 | +++ surveilr/api/server/auth.py 2012-01-04 23:13:32 +0000 | |||
170 | @@ -0,0 +1,48 @@ | |||
171 | 1 | """ | ||
172 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
173 | 3 | |||
174 | 4 | Copyright (C) 2011 Linux2Go | ||
175 | 5 | |||
176 | 6 | This program is free software: you can redistribute it and/or | ||
177 | 7 | modify it under the terms of the GNU Affero General Public License | ||
178 | 8 | as published by the Free Software Foundation, either version 3 of | ||
179 | 9 | the License, or (at your option) any later version. | ||
180 | 10 | |||
181 | 11 | This program is distributed in the hope that it will be useful, | ||
182 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
183 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
184 | 14 | GNU Affero General Public License for more details. | ||
185 | 15 | |||
186 | 16 | You should have received a copy of the GNU Affero General Public | ||
187 | 17 | License along with this program. If not, see | ||
188 | 18 | <http://www.gnu.org/licenses/>. | ||
189 | 19 | |||
190 | 20 | API server auth implementation | ||
191 | 21 | """ | ||
192 | 22 | |||
193 | 23 | from surveilr import models | ||
194 | 24 | |||
195 | 25 | from riakalchemy import NoSuchObjectError | ||
196 | 26 | |||
197 | 27 | class AlwaysRequireAuth(object): | ||
198 | 28 | def __call__(self, environ, status, headers): | ||
199 | 29 | return 'repoze.who.identity' not in environ | ||
200 | 30 | |||
201 | 31 | |||
202 | 32 | class SurveilrAuthPlugin(object): | ||
203 | 33 | def authenticate(self, environ, identity): | ||
204 | 34 | try: | ||
205 | 35 | login = identity['login'] | ||
206 | 36 | password = identity['password'] | ||
207 | 37 | except KeyError: | ||
208 | 38 | return None | ||
209 | 39 | |||
210 | 40 | try: | ||
211 | 41 | user = models.User.get(key=login) | ||
212 | 42 | except NoSuchObjectError: | ||
213 | 43 | return None | ||
214 | 44 | |||
215 | 45 | if user.api_key == password: | ||
216 | 46 | environ['surveilr.user'] = user | ||
217 | 47 | return login | ||
218 | 48 | return None | ||
219 | 0 | 49 | ||
220 | === added file 'surveilr/api/server/factory.py' | |||
221 | --- surveilr/api/server/factory.py 1970-01-01 00:00:00 +0000 | |||
222 | +++ surveilr/api/server/factory.py 2012-01-04 23:13:32 +0000 | |||
223 | @@ -0,0 +1,29 @@ | |||
224 | 1 | """ | ||
225 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
226 | 3 | |||
227 | 4 | Copyright (C) 2011 Linux2Go | ||
228 | 5 | |||
229 | 6 | This program is free software: you can redistribute it and/or | ||
230 | 7 | modify it under the terms of the GNU Affero General Public License | ||
231 | 8 | as published by the Free Software Foundation, either version 3 of | ||
232 | 9 | the License, or (at your option) any later version. | ||
233 | 10 | |||
234 | 11 | This program is distributed in the hope that it will be useful, | ||
235 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
236 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
237 | 14 | GNU Affero General Public License for more details. | ||
238 | 15 | |||
239 | 16 | You should have received a copy of the GNU Affero General Public | ||
240 | 17 | License along with this program. If not, see | ||
241 | 18 | <http://www.gnu.org/licenses/>. | ||
242 | 19 | |||
243 | 20 | API server factory | ||
244 | 21 | """ | ||
245 | 22 | import eventlet | ||
246 | 23 | |||
247 | 24 | def server_factory(global_conf, host, port): | ||
248 | 25 | port = int(port) | ||
249 | 26 | def serve(application): | ||
250 | 27 | socket = eventlet.listen((host, port)) | ||
251 | 28 | eventlet.wsgi.server(socket, application) | ||
252 | 29 | return serve | ||
253 | 0 | 30 | ||
254 | === modified file 'surveilr/defaults.cfg' | |||
255 | --- surveilr/defaults.cfg 2011-12-20 10:59:35 +0000 | |||
256 | +++ surveilr/defaults.cfg 2012-01-04 23:13:32 +0000 | |||
257 | @@ -12,13 +12,22 @@ | |||
258 | 12 | port = 8098 | 12 | port = 8098 |
259 | 13 | 13 | ||
260 | 14 | [server:main] | 14 | [server:main] |
262 | 15 | paste.server_factory = surveilr.api.server:server_factory | 15 | paste.server_factory = surveilr.api.server.factory:server_factory |
263 | 16 | host = 0.0.0.0 | 16 | host = 0.0.0.0 |
264 | 17 | port = 8977 | 17 | port = 8977 |
265 | 18 | 18 | ||
266 | 19 | [composite:main] | 19 | [composite:main] |
267 | 20 | use = egg:Paste#urlmap | 20 | use = egg:Paste#urlmap |
269 | 21 | /surveilr = core | 21 | /surveilr = secured |
270 | 22 | |||
271 | 23 | [pipeline:secured] | ||
272 | 24 | pipeline = who core | ||
273 | 22 | 25 | ||
274 | 23 | [app:core] | 26 | [app:core] |
276 | 24 | paste.app_factory = surveilr.api.server:SurveilrApplication | 27 | paste.app_factory = surveilr.api.server.app:SurveilrApplication |
277 | 28 | |||
278 | 29 | [filter:who] | ||
279 | 30 | use = egg:repoze.who#config | ||
280 | 31 | config_file = %(here)s/who.ini | ||
281 | 32 | log_file = stdout | ||
282 | 33 | log_level = debug | ||
283 | 25 | 34 | ||
284 | === modified file 'surveilr/models.py' | |||
285 | --- surveilr/models.py 2011-12-09 16:17:30 +0000 | |||
286 | +++ surveilr/models.py 2012-01-04 23:13:32 +0000 | |||
287 | @@ -22,6 +22,7 @@ | |||
288 | 22 | from riakalchemy import RiakObject | 22 | from riakalchemy import RiakObject |
289 | 23 | from riakalchemy.types import Integer, String, Dict, RelatedObjects | 23 | from riakalchemy.types import Integer, String, Dict, RelatedObjects |
290 | 24 | 24 | ||
291 | 25 | from surveilr import utils | ||
292 | 25 | 26 | ||
293 | 26 | class Service(RiakObject): | 27 | class Service(RiakObject): |
294 | 27 | """A service that is referenced by many LogEntry's | 28 | """A service that is referenced by many LogEntry's |
295 | @@ -43,9 +44,14 @@ | |||
296 | 43 | """A user of the service""" | 44 | """A user of the service""" |
297 | 44 | bucket_name = 'users' | 45 | bucket_name = 'users' |
298 | 45 | 46 | ||
299 | 47 | api_key = String() | ||
300 | 48 | credentials = Dict() | ||
301 | 46 | messaging_driver = String() | 49 | messaging_driver = String() |
302 | 47 | messaging_address = String() | 50 | messaging_address = String() |
303 | 48 | 51 | ||
304 | 52 | def pre_save(self): | ||
305 | 53 | if not hasattr(self, 'api_key'): | ||
306 | 54 | self.api_key = utils.generate_key() | ||
307 | 49 | 55 | ||
308 | 50 | class LogEntry(RiakObject): | 56 | class LogEntry(RiakObject): |
309 | 51 | """A log entry holding one or more metrics | 57 | """A log entry holding one or more metrics |
310 | 52 | 58 | ||
311 | === added directory 'surveilr/tests/api/server' | |||
312 | === added file 'surveilr/tests/api/server/__init__.py' | |||
313 | --- surveilr/tests/api/server/__init__.py 1970-01-01 00:00:00 +0000 | |||
314 | +++ surveilr/tests/api/server/__init__.py 2012-01-04 23:13:32 +0000 | |||
315 | @@ -0,0 +1,19 @@ | |||
316 | 1 | """ | ||
317 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
318 | 3 | |||
319 | 4 | Copyright (C) 2011 Linux2Go | ||
320 | 5 | |||
321 | 6 | This program is free software: you can redistribute it and/or | ||
322 | 7 | modify it under the terms of the GNU Affero General Public License | ||
323 | 8 | as published by the Free Software Foundation, either version 3 of | ||
324 | 9 | the License, or (at your option) any later version. | ||
325 | 10 | |||
326 | 11 | This program is distributed in the hope that it will be useful, | ||
327 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
328 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
329 | 14 | GNU Affero General Public License for more details. | ||
330 | 15 | |||
331 | 16 | You should have received a copy of the GNU Affero General Public | ||
332 | 17 | License along with this program. If not, see | ||
333 | 18 | <http://www.gnu.org/licenses/>. | ||
334 | 19 | """ | ||
335 | 0 | 20 | ||
336 | === renamed file 'surveilr/tests/api/test_server.py' => 'surveilr/tests/api/server/test_app.py' | |||
337 | --- surveilr/tests/api/test_server.py 2011-12-20 10:59:35 +0000 | |||
338 | +++ surveilr/tests/api/server/test_app.py 2012-01-04 23:13:32 +0000 | |||
339 | @@ -27,8 +27,8 @@ | |||
340 | 27 | from surveilr import models | 27 | from surveilr import models |
341 | 28 | from surveilr import tests | 28 | from surveilr import tests |
342 | 29 | from surveilr import utils | 29 | from surveilr import utils |
345 | 30 | from surveilr.api import server | 30 | from surveilr.api.server import app |
346 | 31 | from surveilr.api.server import SurveilrApplication | 31 | from surveilr.api.server.app import SurveilrApplication |
347 | 32 | 32 | ||
348 | 33 | 33 | ||
349 | 34 | class APIServerTests(tests.TestCase): | 34 | class APIServerTests(tests.TestCase): |
350 | @@ -36,12 +36,32 @@ | |||
351 | 36 | super(APIServerTests, self).setUp() | 36 | super(APIServerTests, self).setUp() |
352 | 37 | self.application = SurveilrApplication({}) | 37 | self.application = SurveilrApplication({}) |
353 | 38 | 38 | ||
354 | 39 | def test_create_user_denied(self): | ||
355 | 40 | """Creating a user is refused for non-privileged users""" | ||
356 | 41 | req = Request.blank('/users', | ||
357 | 42 | method='POST', | ||
358 | 43 | POST=json.dumps({'messaging_driver': 'fake', | ||
359 | 44 | 'messaging_address': 'foo'})) | ||
360 | 45 | class FakeUser(object): | ||
361 | 46 | credentials = {'admin': False} | ||
362 | 47 | |||
363 | 48 | req.environ['surveilr.user'] = FakeUser() | ||
364 | 49 | resp = self.application(req) | ||
365 | 50 | self.assertEquals(resp.status_int, 403) | ||
366 | 51 | |||
367 | 39 | def test_create_retrieve_user(self): | 52 | def test_create_retrieve_user(self): |
369 | 40 | """Create, retrieve, delete, attempt to retrieve again""" | 53 | self._test_create_retrieve_user() |
370 | 54 | |||
371 | 55 | def test_create_retrieve_admin_user(self): | ||
372 | 56 | self._test_create_retrieve_user(admin=True) | ||
373 | 57 | |||
374 | 58 | def _test_create_retrieve_user(self, admin=False): | ||
375 | 59 | """Create, retrieve, delete, attempt to retrieve again (user)""" | ||
376 | 41 | req = Request.blank('/users', | 60 | req = Request.blank('/users', |
377 | 42 | method='POST', | 61 | method='POST', |
378 | 43 | POST=json.dumps({'messaging_driver': 'fake', | 62 | POST=json.dumps({'messaging_driver': 'fake', |
380 | 44 | 'messaging_address': 'foo'})) | 63 | 'messaging_address': 'foo', |
381 | 64 | 'admin': True})) | ||
382 | 45 | resp = self.application(req) | 65 | resp = self.application(req) |
383 | 46 | self.assertEquals(resp.status_int, 200) | 66 | self.assertEquals(resp.status_int, 200) |
384 | 47 | 67 | ||
385 | @@ -54,6 +74,7 @@ | |||
386 | 54 | user = json.loads(resp.body) | 74 | user = json.loads(resp.body) |
387 | 55 | self.assertEquals(user['messaging_driver'], 'fake') | 75 | self.assertEquals(user['messaging_driver'], 'fake') |
388 | 56 | self.assertEquals(user['messaging_address'], 'foo') | 76 | self.assertEquals(user['messaging_address'], 'foo') |
389 | 77 | self.assertEquals(user['admin'], True) | ||
390 | 57 | 78 | ||
391 | 58 | req = Request.blank('/users/%s' % service_id, method='DELETE') | 79 | req = Request.blank('/users/%s' % service_id, method='DELETE') |
392 | 59 | resp = self.application(req) | 80 | resp = self.application(req) |
393 | @@ -81,7 +102,7 @@ | |||
394 | 81 | self.assertEquals(resp.status_int, 200) | 102 | self.assertEquals(resp.status_int, 200) |
395 | 82 | 103 | ||
396 | 83 | def test_create_retrieve_service(self): | 104 | def test_create_retrieve_service(self): |
398 | 84 | """Create, retrieve, delete, attempt to retrieve again""" | 105 | """Create, retrieve, delete, attempt to retrieve again (service)""" |
399 | 85 | req = Request.blank('/services', | 106 | req = Request.blank('/services', |
400 | 86 | method='POST', | 107 | method='POST', |
401 | 87 | POST=json.dumps({'name': 'this_or_the_other'})) | 108 | POST=json.dumps({'name': 'this_or_the_other'})) |
402 | @@ -149,7 +170,7 @@ | |||
403 | 149 | 'timestamp': 13217362355575, | 170 | 'timestamp': 13217362355575, |
404 | 150 | 'metrics': {'duration': 85000, | 171 | 'metrics': {'duration': 85000, |
405 | 151 | 'response_size': 12435}})) | 172 | 'response_size': 12435}})) |
407 | 152 | with mock.patch('surveilr.api.server.eventlet') as eventlet: | 173 | with mock.patch('surveilr.api.server.app.eventlet') as eventlet: |
408 | 153 | resp = self.application(req) | 174 | resp = self.application(req) |
409 | 154 | 175 | ||
410 | 155 | self.assertEquals(eventlet.spawn_n.call_args[0][0], | 176 | self.assertEquals(eventlet.spawn_n.call_args[0][0], |
411 | @@ -167,12 +188,12 @@ | |||
412 | 167 | resp = self.application(req) | 188 | resp = self.application(req) |
413 | 168 | self.assertEquals(resp.status_int, 404) | 189 | self.assertEquals(resp.status_int, 404) |
414 | 169 | 190 | ||
417 | 170 | @mock.patch('surveilr.api.server.eventlet', spec=['listen', 'wsgi']) | 191 | @mock.patch('surveilr.api.server.app.eventlet', spec=['listen', 'wsgi']) |
418 | 171 | @mock.patch('surveilr.api.server.riakalchemy', spec=['connect']) | 192 | @mock.patch('surveilr.api.server.app.riakalchemy', spec=['connect']) |
419 | 172 | def test_main(self, riakalchemy, eventlet): | 193 | def test_main(self, riakalchemy, eventlet): |
420 | 173 | socket_sentinel = mock.sentinel.return_value | 194 | socket_sentinel = mock.sentinel.return_value |
421 | 174 | eventlet.listen.return_value = socket_sentinel | 195 | eventlet.listen.return_value = socket_sentinel |
423 | 175 | server.main() | 196 | app.main() |
424 | 176 | 197 | ||
425 | 177 | riakalchemy.connect.assert_called_with(host='127.0.0.1', port=8098) | 198 | riakalchemy.connect.assert_called_with(host='127.0.0.1', port=8098) |
426 | 178 | eventlet.listen.assert_called_with(('', 9877)) | 199 | eventlet.listen.assert_called_with(('', 9877)) |
427 | 179 | 200 | ||
428 | === added file 'surveilr/tests/api/server/test_auth.py' | |||
429 | --- surveilr/tests/api/server/test_auth.py 1970-01-01 00:00:00 +0000 | |||
430 | +++ surveilr/tests/api/server/test_auth.py 2012-01-04 23:13:32 +0000 | |||
431 | @@ -0,0 +1,82 @@ | |||
432 | 1 | """ | ||
433 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
434 | 3 | |||
435 | 4 | Copyright (C) 2011 Linux2Go | ||
436 | 5 | |||
437 | 6 | This program is free software: you can redistribute it and/or | ||
438 | 7 | modify it under the terms of the GNU Affero General Public License | ||
439 | 8 | as published by the Free Software Foundation, either version 3 of | ||
440 | 9 | the License, or (at your option) any later version. | ||
441 | 10 | |||
442 | 11 | This program is distributed in the hope that it will be useful, | ||
443 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
444 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
445 | 14 | GNU Affero General Public License for more details. | ||
446 | 15 | |||
447 | 16 | You should have received a copy of the GNU Affero General Public | ||
448 | 17 | License along with this program. If not, see | ||
449 | 18 | <http://www.gnu.org/licenses/>. | ||
450 | 19 | |||
451 | 20 | Tests for API server auth | ||
452 | 21 | """ | ||
453 | 22 | |||
454 | 23 | from surveilr import models | ||
455 | 24 | from surveilr import tests | ||
456 | 25 | from surveilr.api.server import auth | ||
457 | 26 | |||
458 | 27 | class TestAPIServerAuth(tests.TestCase): | ||
459 | 28 | def test_AlwaysRequireAuth_unauthenticated(self): | ||
460 | 29 | decider = auth.AlwaysRequireAuth() | ||
461 | 30 | self.assertEquals(decider({}, '200 OK', {}), True) | ||
462 | 31 | |||
463 | 32 | def test_AlwaysRequireAuth_already_authenticated(self): | ||
464 | 33 | decider = auth.AlwaysRequireAuth() | ||
465 | 34 | self.assertEquals(decider({'repoze.who.identity': 'someone'}, '200 OK', {}), False) | ||
466 | 35 | |||
467 | 36 | class TestSurveilrAuthPlugin(tests.TestCase): | ||
468 | 37 | def test_authenticate_invcomplete_identity(self): | ||
469 | 38 | env = {} | ||
470 | 39 | identity = {'password': 'apikey'} | ||
471 | 40 | |||
472 | 41 | self.assertIsNone(auth.SurveilrAuthPlugin().authenticate(env, | ||
473 | 42 | identity)) | ||
474 | 43 | self.assertEquals(env, {}) | ||
475 | 44 | |||
476 | 45 | |||
477 | 46 | def test_authenticate_invalid_identity(self): | ||
478 | 47 | env = {} | ||
479 | 48 | identity = {'login': 'testuser', | ||
480 | 49 | 'password': 'apikey'} | ||
481 | 50 | |||
482 | 51 | self.assertIsNone(auth.SurveilrAuthPlugin().authenticate(env, | ||
483 | 52 | identity)) | ||
484 | 53 | self.assertEquals(env, {}) | ||
485 | 54 | |||
486 | 55 | |||
487 | 56 | def test_authenticate_valid_credentials(self): | ||
488 | 57 | env = {} | ||
489 | 58 | |||
490 | 59 | user = models.User() | ||
491 | 60 | user.save() | ||
492 | 61 | identity = {'login': user.key, | ||
493 | 62 | 'password': user.api_key} | ||
494 | 63 | |||
495 | 64 | self.assertEquals(auth.SurveilrAuthPlugin().authenticate(env, | ||
496 | 65 | identity), | ||
497 | 66 | user.key) | ||
498 | 67 | self.assertEquals(env['surveilr.user'].key, user.key) | ||
499 | 68 | |||
500 | 69 | def test_authenticate_wrong_key(self): | ||
501 | 70 | env = {} | ||
502 | 71 | |||
503 | 72 | user = models.User() | ||
504 | 73 | user.save() | ||
505 | 74 | self.addCleanup(user.delete) | ||
506 | 75 | |||
507 | 76 | identity = {'login': user.key, | ||
508 | 77 | 'password': 'not the right key'} | ||
509 | 78 | |||
510 | 79 | self.assertIsNone(auth.SurveilrAuthPlugin().authenticate(env, | ||
511 | 80 | identity), | ||
512 | 81 | user.key) | ||
513 | 82 | self.assertEquals(env, {}) | ||
514 | 0 | 83 | ||
515 | === added file 'surveilr/tests/api/server/test_factory.py' | |||
516 | --- surveilr/tests/api/server/test_factory.py 1970-01-01 00:00:00 +0000 | |||
517 | +++ surveilr/tests/api/server/test_factory.py 2012-01-04 23:13:32 +0000 | |||
518 | @@ -0,0 +1,43 @@ | |||
519 | 1 | """ | ||
520 | 2 | Surveilr - Log aggregation, analysis and visualisation | ||
521 | 3 | |||
522 | 4 | Copyright (C) 2011 Linux2Go | ||
523 | 5 | |||
524 | 6 | This program is free software: you can redistribute it and/or | ||
525 | 7 | modify it under the terms of the GNU Affero General Public License | ||
526 | 8 | as published by the Free Software Foundation, either version 3 of | ||
527 | 9 | the License, or (at your option) any later version. | ||
528 | 10 | |||
529 | 11 | This program is distributed in the hope that it will be useful, | ||
530 | 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
531 | 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
532 | 14 | GNU Affero General Public License for more details. | ||
533 | 15 | |||
534 | 16 | You should have received a copy of the GNU Affero General Public | ||
535 | 17 | License along with this program. If not, see | ||
536 | 18 | <http://www.gnu.org/licenses/>. | ||
537 | 19 | |||
538 | 20 | Tests for API server factory | ||
539 | 21 | """ | ||
540 | 22 | |||
541 | 23 | import mock | ||
542 | 24 | |||
543 | 25 | from surveilr import tests | ||
544 | 26 | from surveilr.api.server import factory | ||
545 | 27 | |||
546 | 28 | class APIServerFactoryTests(tests.TestCase): | ||
547 | 29 | def test_server_factory_returns_callable(self): | ||
548 | 30 | self.assertTrue(callable(factory.server_factory({}, 'somehost', 1234))) | ||
549 | 31 | |||
550 | 32 | @mock.patch('surveilr.api.server.factory.eventlet') | ||
551 | 33 | def test_server_factory_returns_server(self, eventlet): | ||
552 | 34 | testhost = 'somehost' | ||
553 | 35 | testport = '1234' | ||
554 | 36 | serve = factory.server_factory({}, testhost, testport) | ||
555 | 37 | app = mock.sentinel.app | ||
556 | 38 | |||
557 | 39 | serve(app) | ||
558 | 40 | |||
559 | 41 | eventlet.listen.assert_called_with((testhost, int(testport))) | ||
560 | 42 | eventlet.wsgi.server.assert_called_with(eventlet.listen.return_value, | ||
561 | 43 | app) | ||
562 | 0 | 44 | ||
563 | === modified file 'surveilr/tests/test_utils.py' | |||
564 | --- surveilr/tests/test_utils.py 2011-12-20 09:49:02 +0000 | |||
565 | +++ surveilr/tests/test_utils.py 2012-01-04 23:13:32 +0000 | |||
566 | @@ -23,6 +23,7 @@ | |||
567 | 23 | import json | 23 | import json |
568 | 24 | import time | 24 | import time |
569 | 25 | import mock | 25 | import mock |
570 | 26 | import string | ||
571 | 26 | 27 | ||
572 | 27 | from surveilr import models | 28 | from surveilr import models |
573 | 28 | from surveilr import tests | 29 | from surveilr import tests |
574 | @@ -94,3 +95,8 @@ | |||
575 | 94 | self.assertEquals(utils.truncate(133, 20), 120) | 95 | self.assertEquals(utils.truncate(133, 20), 120) |
576 | 95 | self.assertEquals(utils.truncate(133, 100), 100) | 96 | self.assertEquals(utils.truncate(133, 100), 100) |
577 | 96 | self.assertEquals(utils.truncate(133, 200), 0) | 97 | self.assertEquals(utils.truncate(133, 200), 0) |
578 | 98 | |||
579 | 99 | def test_generate_key(self): | ||
580 | 100 | key = utils.generate_key() | ||
581 | 101 | self.assertEquals(len(key), 32) | ||
582 | 102 | self.assertEquals(len(key.strip(string.letters + string.digits)), 0) | ||
583 | 97 | 103 | ||
584 | === modified file 'surveilr/utils.py' | |||
585 | --- surveilr/utils.py 2011-12-11 22:11:47 +0000 | |||
586 | +++ surveilr/utils.py 2012-01-04 23:13:32 +0000 | |||
587 | @@ -22,6 +22,8 @@ | |||
588 | 22 | 22 | ||
589 | 23 | import httplib2 | 23 | import httplib2 |
590 | 24 | import json | 24 | import json |
591 | 25 | import random | ||
592 | 26 | import string | ||
593 | 25 | 27 | ||
594 | 26 | 28 | ||
595 | 27 | def truncate(number, rounding_factor): | 29 | def truncate(number, rounding_factor): |
596 | @@ -39,6 +41,10 @@ | |||
597 | 39 | return (int(number) / int(rounding_factor)) * int(rounding_factor) | 41 | return (int(number) / int(rounding_factor)) * int(rounding_factor) |
598 | 40 | 42 | ||
599 | 41 | 43 | ||
600 | 44 | def generate_key(): | ||
601 | 45 | return ''.join([random.choice(string.letters + string.digits) | ||
602 | 46 | for x in range(32)]) | ||
603 | 47 | |||
604 | 42 | def enhance_data_point(data_point): | 48 | def enhance_data_point(data_point): |
605 | 43 | http = httplib2.Http(timeout=10) | 49 | http = httplib2.Http(timeout=10) |
606 | 44 | 50 | ||
607 | 45 | 51 | ||
608 | === added file 'surveilr/who.ini' | |||
609 | --- surveilr/who.ini 1970-01-01 00:00:00 +0000 | |||
610 | +++ surveilr/who.ini 2012-01-04 23:13:32 +0000 | |||
611 | @@ -0,0 +1,23 @@ | |||
612 | 1 | [plugin:basicauth] | ||
613 | 2 | use = repoze.who.plugins.basicauth:make_plugin | ||
614 | 3 | realm = 'Surveilr' | ||
615 | 4 | |||
616 | 5 | [plugin:surveilrauth] | ||
617 | 6 | use = surveilr.api.server.auth:SurveilrAuthPlugin | ||
618 | 7 | |||
619 | 8 | [general] | ||
620 | 9 | request_classifier = repoze.who.classifiers:default_request_classifier | ||
621 | 10 | challenge_decider = surveilr.api.server.auth:AlwaysRequireAuth | ||
622 | 11 | remote_user_key = REMOTE_USER | ||
623 | 12 | |||
624 | 13 | [identifiers] | ||
625 | 14 | plugins = | ||
626 | 15 | basicauth | ||
627 | 16 | |||
628 | 17 | [authenticators] | ||
629 | 18 | plugins = | ||
630 | 19 | surveilrauth | ||
631 | 20 | |||
632 | 21 | [challengers] | ||
633 | 22 | plugins = | ||
634 | 23 | basicauth | ||
635 | 0 | 24 | ||
636 | === modified file 'tools/pip-requirements.txt' | |||
637 | --- tools/pip-requirements.txt 2011-12-20 10:59:35 +0000 | |||
638 | +++ tools/pip-requirements.txt 2012-01-04 23:13:32 +0000 | |||
639 | @@ -13,3 +13,4 @@ | |||
640 | 13 | paste | 13 | paste |
641 | 14 | PasteScript | 14 | PasteScript |
642 | 15 | -e hg+https://code.google.com/p/soren-bulksms/#egg=BulkSMS | 15 | -e hg+https://code.google.com/p/soren-bulksms/#egg=BulkSMS |
643 | 16 | repoze.who |