Merge lp:~facundo/magicicada-client/remove-web into lp:magicicada-client
- remove-web
- Merge into trunk
Proposed by
Facundo Batista
Status: | Merged |
---|---|
Approved by: | Natalia Bidart |
Approved revision: | 1435 |
Merged at revision: | 1434 |
Proposed branch: | lp:~facundo/magicicada-client/remove-web |
Merge into: | lp:magicicada-client |
Diff against target: |
2432 lines (+49/-2144) 17 files modified
dependencies.txt (+0/-1) ubuntuone/proxy/tunnel_server.py (+2/-1) ubuntuone/syncdaemon/action_queue.py (+7/-65) ubuntuone/syncdaemon/event_queue.py (+1/-6) ubuntuone/syncdaemon/tests/test_action_queue.py (+2/-121) ubuntuone/utils/tests/test_common.py (+1/-17) ubuntuone/utils/tests/test_gsettings.py (+36/-6) ubuntuone/utils/webclient/__init__.py (+0/-47) ubuntuone/utils/webclient/common.py (+0/-189) ubuntuone/utils/webclient/libsoup.py (+0/-137) ubuntuone/utils/webclient/restful.py (+0/-94) ubuntuone/utils/webclient/tests/__init__.py (+0/-65) ubuntuone/utils/webclient/tests/test_restful.py (+0/-233) ubuntuone/utils/webclient/tests/test_timestamp.py (+0/-141) ubuntuone/utils/webclient/tests/test_webclient.py (+0/-753) ubuntuone/utils/webclient/timestamp.py (+0/-93) ubuntuone/utils/webclient/txweb.py (+0/-175) |
To merge this branch: | bzr merge lp:~facundo/magicicada-client/remove-web |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Natalia Bidart | Approve | ||
Review via email: mp+314692@code.launchpad.net |
Commit message
Remove everything related to hitting server functionality through (web) API.
Description of the change
Remove everything related to hitting server functionality through (web) API.
This is not needed anymore after implementing public files through API and deprecating the old-days Share offering not-by-username.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'dependencies.txt' |
2 | --- dependencies.txt 2016-09-17 01:06:23 +0000 |
3 | +++ dependencies.txt 2017-01-13 11:56:59 +0000 |
4 | @@ -4,7 +4,6 @@ |
5 | python-dirspec |
6 | python-distutils-extra |
7 | python-gi |
8 | -python-httplib2 |
9 | python-protobuf |
10 | python-pyinotify |
11 | python-qt4-dbus |
12 | |
13 | === modified file 'ubuntuone/proxy/tunnel_server.py' |
14 | --- ubuntuone/proxy/tunnel_server.py 2016-06-01 18:28:19 +0000 |
15 | +++ ubuntuone/proxy/tunnel_server.py 2017-01-13 11:56:59 +0000 |
16 | @@ -1,6 +1,7 @@ |
17 | # -*- coding: utf-8 -*- |
18 | # |
19 | # Copyright 2012-2013 Canonical Ltd. |
20 | +# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros) |
21 | # |
22 | # This program is free software: you can redistribute it and/or modify it |
23 | # under the terms of the GNU General Public License version 3, as published |
24 | @@ -60,7 +61,7 @@ |
25 | |
26 | from ubuntuone.clientdefs import NAME |
27 | from ubuntuone.keyring import Keyring |
28 | -from ubuntuone.utils.webclient import gsettings |
29 | +from ubuntuone.utils import gsettings |
30 | from ubuntuone.proxy.common import ( |
31 | BaseTunnelProtocol, |
32 | CRLF, |
33 | |
34 | === modified file 'ubuntuone/syncdaemon/action_queue.py' |
35 | --- ubuntuone/syncdaemon/action_queue.py 2016-11-07 01:13:32 +0000 |
36 | +++ ubuntuone/syncdaemon/action_queue.py 2017-01-13 11:56:59 +0000 |
37 | @@ -1,7 +1,7 @@ |
38 | # -*- coding: utf-8 -*- |
39 | # |
40 | # Copyright 2009-2015 Canonical Ltd. |
41 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
42 | +# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros) |
43 | # |
44 | # This program is free software: you can redistribute it and/or modify it |
45 | # under the terms of the GNU General Public License version 3, as published |
46 | @@ -33,15 +33,12 @@ |
47 | import logging |
48 | import os |
49 | import random |
50 | -import re |
51 | import tempfile |
52 | import traceback |
53 | import zlib |
54 | |
55 | from collections import deque, defaultdict |
56 | from functools import partial |
57 | -from urllib import urlencode |
58 | -from urlparse import urlparse |
59 | |
60 | import OpenSSL.SSL |
61 | |
62 | @@ -62,19 +59,14 @@ |
63 | from ubuntuone.storageprotocol.context import get_ssl_context |
64 | from ubuntuone.syncdaemon.interfaces import IActionQueue, IMarker |
65 | from ubuntuone.syncdaemon.logger import mklog, TRACE |
66 | -from ubuntuone.syncdaemon.volume_manager import ACCESS_LEVEL_RW |
67 | from ubuntuone.syncdaemon import config, offload_queue |
68 | from ubuntuone.syncdaemon import tunnel_runner |
69 | -from ubuntuone.utils.webclient import txweb |
70 | |
71 | logger = logging.getLogger("ubuntuone.SyncDaemon.ActionQueue") |
72 | |
73 | # I want something which repr() is "---" *without* the quotes :) |
74 | UNKNOWN = type('', (), {'__repr__': lambda _: '---'})() |
75 | |
76 | -# Regular expression to validate an e-mail address |
77 | -EREGEX = "^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$" |
78 | - |
79 | # progress threshold to emit a download/upload progress event: 64Kb |
80 | TRANSFER_PROGRESS_THRESHOLD = 64 * 1024 |
81 | |
82 | @@ -911,27 +903,6 @@ |
83 | return defer.succeed((self.host, self.port)) |
84 | |
85 | @defer.inlineCallbacks |
86 | - def webcall(self, iri, **kwargs): |
87 | - """Perform a web call to the api servers.""" |
88 | - webclient = yield self.get_webclient(iri) |
89 | - # FIXME: we need to review these requests after credentials change |
90 | - response = yield webclient.request( |
91 | - iri, auth_credentials=self.credentials, **kwargs) |
92 | - defer.returnValue(response) |
93 | - |
94 | - @defer.inlineCallbacks |
95 | - def get_webclient(self, iri): |
96 | - """Get the webclient, creating it if needed.""" |
97 | - uri = txweb.WebClient().iri_to_uri(iri) |
98 | - host = urlparse(uri).netloc.split(":")[0] |
99 | - ssl_context = get_ssl_context(self.disable_ssl_verify, host) |
100 | - connector = yield self.tunnel_runner.get_client() |
101 | - webclient = txweb.WebClient( |
102 | - connector=connector, appname=clientdefs.NAME, |
103 | - context_factory=ssl_context) |
104 | - defer.returnValue(webclient) |
105 | - |
106 | - @defer.inlineCallbacks |
107 | def _make_connection(self, result): |
108 | """Do the real connect call.""" |
109 | host, port = result |
110 | @@ -1892,7 +1863,7 @@ |
111 | """Offer a share to somebody.""" |
112 | |
113 | __slots__ = ('node_id', 'share_to', 'name', 'access_level', |
114 | - 'marker', 'use_http', 'path') |
115 | + 'marker', 'path') |
116 | possible_markers = 'node_id', |
117 | logged_attrs = ActionQueueCommand.logged_attrs + __slots__ |
118 | |
119 | @@ -1904,47 +1875,18 @@ |
120 | self.name = name |
121 | self.access_level = access_level |
122 | self.marker = marker |
123 | - self.use_http = False |
124 | self.path = path |
125 | |
126 | - if share_to and re.match(EREGEX, share_to): |
127 | - self.use_http = True |
128 | - |
129 | - @defer.inlineCallbacks |
130 | - def _create_share_http(self, node_id, user, name, read_only): |
131 | - """Create a share using the HTTP Web API method.""" |
132 | - |
133 | - iri = u"https://one.ubuntu.com/files/api/offer_share/" |
134 | - data = dict(offer_to_email=user, |
135 | - read_only=read_only, |
136 | - node_id=node_id, |
137 | - share_name=name) |
138 | - pdata = urlencode(data) |
139 | - yield self.action_queue.webcall(iri, method="POST", post_content=pdata) |
140 | - |
141 | def _run(self): |
142 | """Do the actual running.""" |
143 | - if self.use_http: |
144 | - # External user, do the HTTP REST method |
145 | - return self._create_share_http( |
146 | - self.node_id, self.share_to, self.name, |
147 | - self.access_level != ACCESS_LEVEL_RW) |
148 | - else: |
149 | - return self.action_queue.client.create_share(self.node_id, |
150 | - self.share_to, |
151 | - self.name, |
152 | - self.access_level) |
153 | + return self.action_queue.client.create_share( |
154 | + self.node_id, self.share_to, self.name, self.access_level) |
155 | |
156 | def handle_success(self, success): |
157 | """It worked! Push the event.""" |
158 | - # We don't get a share_id back from the HTTP REST method |
159 | - if not self.use_http: |
160 | - self.action_queue.event_queue.push('AQ_CREATE_SHARE_OK', |
161 | - share_id=success.share_id, |
162 | - marker=self.marker) |
163 | - else: |
164 | - self.action_queue.event_queue.push('AQ_SHARE_INVITATION_SENT', |
165 | - marker=self.marker) |
166 | + self.action_queue.event_queue.push( |
167 | + 'AQ_CREATE_SHARE_OK', share_id=success.share_id, |
168 | + marker=self.marker) |
169 | |
170 | def handle_failure(self, failure): |
171 | """It didn't work! Push the event.""" |
172 | |
173 | === modified file 'ubuntuone/syncdaemon/event_queue.py' |
174 | --- ubuntuone/syncdaemon/event_queue.py 2015-09-19 23:15:50 +0000 |
175 | +++ ubuntuone/syncdaemon/event_queue.py 2017-01-13 11:56:59 +0000 |
176 | @@ -1,9 +1,5 @@ |
177 | -# ubuntuone.syncdaemon.event_queue - Event queuing |
178 | -# |
179 | -# Authors: Facundo Batista <facundo@canonical.com> |
180 | -# Manuel de la Pena <manuel@canonical.com> |
181 | -# |
182 | # Copyright 2009-2012 Canonical Ltd. |
183 | +# Copyright 2016-2017 Chicharreros |
184 | # |
185 | # This program is free software: you can redistribute it and/or modify it |
186 | # under the terms of the GNU General Public License version 3, as published |
187 | @@ -79,7 +75,6 @@ |
188 | 'AQ_UPLOAD_ERROR': ('share_id', 'node_id', 'error', 'hash'), |
189 | 'AQ_SHARES_LIST': ('shares_list',), |
190 | 'AQ_LIST_SHARES_ERROR': ('error',), |
191 | - 'AQ_SHARE_INVITATION_SENT': ('marker',), |
192 | 'AQ_CREATE_SHARE_OK': ('share_id', 'marker'), |
193 | 'AQ_CREATE_SHARE_ERROR': ('marker', 'error'), |
194 | 'AQ_DELETE_SHARE_OK': ('share_id',), |
195 | |
196 | === modified file 'ubuntuone/syncdaemon/tests/test_action_queue.py' |
197 | --- ubuntuone/syncdaemon/tests/test_action_queue.py 2016-12-06 02:00:29 +0000 |
198 | +++ ubuntuone/syncdaemon/tests/test_action_queue.py 2017-01-13 11:56:59 +0000 |
199 | @@ -1,7 +1,7 @@ |
200 | # -*- coding: utf-8 -*- |
201 | # |
202 | # Copyright 2009-2015 Canonical Ltd. |
203 | -# Copyright 2016 Chicharreros |
204 | +# Copyright 2016-2017 Chicharreros |
205 | # |
206 | # This program is free software: you can redistribute it and/or modify it |
207 | # under the terms of the GNU General Public License version 3, as published |
208 | @@ -86,10 +86,7 @@ |
209 | from ubuntuone.syncdaemon.event_queue import EventQueue, EVENTS |
210 | from ubuntuone.syncdaemon import offload_queue |
211 | from ubuntuone.syncdaemon.marker import MDMarker |
212 | -from ubuntuone.syncdaemon.volume_manager import ( |
213 | - ACCESS_LEVEL_RO, |
214 | - ACCESS_LEVEL_RW, |
215 | -) |
216 | +from ubuntuone.syncdaemon.volume_manager import ACCESS_LEVEL_RO |
217 | |
218 | PATH = os.path.join(u'~', u'Documents', u'pdfs', u'moño', u'') |
219 | NAME = u'UDF-me' |
220 | @@ -435,56 +432,6 @@ |
221 | self.assertEqual(defined_args[0], 'self') |
222 | self.assertEqual(set(defined_args[1:]), set(evtargs)) |
223 | |
224 | - @defer.inlineCallbacks |
225 | - def test_get_webclient_called_with_iri(self): |
226 | - """The call to get_webclient includes the iri.""" |
227 | - called_args = [] |
228 | - real_get_webclient = action_queue.ActionQueue.get_webclient |
229 | - |
230 | - def fake_get_webclient(aq, *args): |
231 | - """A fake get_webclient.""" |
232 | - called_args.append(args) |
233 | - return real_get_webclient(aq, *args) |
234 | - |
235 | - self.patch(action_queue.ActionQueue, "get_webclient", |
236 | - fake_get_webclient) |
237 | - self.patch(action_queue.txweb.WebClient, "request", |
238 | - lambda *args, **kwargs: defer.succeed(None)) |
239 | - |
240 | - yield self.action_queue.webcall(self.fake_iri) |
241 | - self.assertEqual(called_args, [(self.fake_iri,)]) |
242 | - |
243 | - @defer.inlineCallbacks |
244 | - def test_get_webclient(self): |
245 | - """The webclient is created every time.""" |
246 | - webclient1 = yield self.action_queue.get_webclient(self.fake_iri) |
247 | - webclient2 = yield self.action_queue.get_webclient(self.fake_iri) |
248 | - self.assertNotEqual(webclient1, webclient2) |
249 | - |
250 | - @defer.inlineCallbacks |
251 | - def test_get_webclient_creates_context_with_host(self): |
252 | - """The ssl context is created with the right host.""" |
253 | - used_host = [] |
254 | - |
255 | - def fake_get_ssl_context(disable_ssl_verify, host): |
256 | - """The host is used to call get_ssl_context.""" |
257 | - used_host.append(host) |
258 | - |
259 | - self.patch(action_queue, "get_ssl_context", fake_get_ssl_context) |
260 | - yield self.action_queue.get_webclient(self.fake_iri) |
261 | - self.assertEqual(used_host, [self.fake_host]) |
262 | - |
263 | - @defer.inlineCallbacks |
264 | - def test_get_webclient_uses_just_created_context(self): |
265 | - """The freshly created context is used to create the webclient.""" |
266 | - calls = [] |
267 | - fake_context = object() |
268 | - self.patch(action_queue, "get_ssl_context", lambda *args: fake_context) |
269 | - self.patch(action_queue.txweb.WebClient, "__init__", |
270 | - lambda *args, **kwargs: calls.append(kwargs)) |
271 | - yield self.action_queue.get_webclient(self.fake_iri) |
272 | - self.assertEqual(calls[1]["context_factory"], fake_context) |
273 | - |
274 | |
275 | class TestLoggingStorageClient(TwistedTestCase): |
276 | """Tests for ensuring magic hash dont show in logs.""" |
277 | @@ -3716,51 +3663,6 @@ |
278 | yield super(CreateShareTestCase, self).setUp() |
279 | self.request_queue = RequestQueue(action_queue=self.action_queue) |
280 | |
281 | - @defer.inlineCallbacks |
282 | - def test_access_level_modify_http(self): |
283 | - """Test proper handling of the access level in the http case.""" |
284 | - # replace _create_share_http with a fake, just to check the args |
285 | - d = defer.Deferred() |
286 | - |
287 | - def check_create_http(self, node_id, user, name, read_only): |
288 | - """Fire the deferred with the args.""" |
289 | - d.callback((node_id, user, name, read_only)) |
290 | - |
291 | - self.patch(CreateShare, "_create_share_http", check_create_http) |
292 | - command = CreateShare(self.request_queue, 'node_id', |
293 | - 'share_to@example.com', 'share_name', |
294 | - ACCESS_LEVEL_RW, 'marker', 'path') |
295 | - self.assertTrue(command.use_http, 'CreateShare should be in http mode') |
296 | - |
297 | - command._run() |
298 | - node_id, user, name, read_only = yield d |
299 | - self.assertEqual('node_id', node_id) |
300 | - self.assertEqual('share_to@example.com', user) |
301 | - self.assertEqual('share_name', name) |
302 | - self.assertFalse(read_only) |
303 | - |
304 | - @defer.inlineCallbacks |
305 | - def test_access_level_view_http(self): |
306 | - """Test proper handling of the access level in the http case.""" |
307 | - # replace _create_share_http with a fake, just to check the args |
308 | - d = defer.Deferred() |
309 | - |
310 | - def check_create_http(self, node_id, user, name, read_only): |
311 | - """Fire the deferred with the args.""" |
312 | - d.callback((node_id, user, name, read_only)) |
313 | - |
314 | - self.patch(CreateShare, "_create_share_http", check_create_http) |
315 | - command = CreateShare(self.request_queue, 'node_id', |
316 | - 'share_to@example.com', 'share_name', |
317 | - ACCESS_LEVEL_RO, 'marker', 'path') |
318 | - self.assertTrue(command.use_http, 'CreateShare should be in http mode') |
319 | - command._run() |
320 | - node_id, user, name, read_only = yield d |
321 | - self.assertEqual('node_id', node_id) |
322 | - self.assertEqual('share_to@example.com', user) |
323 | - self.assertEqual('share_name', name) |
324 | - self.assertTrue(read_only) |
325 | - |
326 | def test_possible_markers(self): |
327 | """Test that it returns the correct values.""" |
328 | cmd = CreateShare(self.request_queue, 'node_id', 'shareto@example.com', |
329 | @@ -3768,27 +3670,6 @@ |
330 | res = [getattr(cmd, x) for x in cmd.possible_markers] |
331 | self.assertEqual(res, ['node_id']) |
332 | |
333 | - def test_handle_success_sends_invitation(self): |
334 | - """The success handler sends the AQ_SHARE_INVITATION_SENT event.""" |
335 | - marker_id = "marker" |
336 | - share_name = "share name" |
337 | - share_to = "share_to@example.com" |
338 | - |
339 | - class MockResult(object): |
340 | - """A mock result.""" |
341 | - share_id = SHARE |
342 | - |
343 | - mock_success = MockResult() |
344 | - cmd = CreateShare(self.request_queue, NODE, share_to, |
345 | - share_name, ACCESS_LEVEL_RO, marker_id, 'path') |
346 | - cmd.log = cmd.make_logger() |
347 | - cmd.use_http = True |
348 | - cmd.handle_success(mock_success) |
349 | - |
350 | - event_params = {'marker': marker_id} |
351 | - events = [('AQ_SHARE_INVITATION_SENT', event_params)] |
352 | - self.assertEqual(events, cmd.action_queue.event_queue.events) |
353 | - |
354 | def test_path_locking(self): |
355 | """Test that it acquires correctly the path lock.""" |
356 | t = [] |
357 | |
358 | === renamed file 'ubuntuone/utils/webclient/gsettings.py' => 'ubuntuone/utils/gsettings.py' |
359 | === modified file 'ubuntuone/utils/tests/test_common.py' |
360 | --- ubuntuone/utils/tests/test_common.py 2016-05-29 00:50:05 +0000 |
361 | +++ ubuntuone/utils/tests/test_common.py 2017-01-13 11:56:59 +0000 |
362 | @@ -1,7 +1,7 @@ |
363 | # -*- coding: utf-8 -*- |
364 | # |
365 | # Copyright 2011-2012 Canonical Ltd. |
366 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
367 | +# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros) |
368 | # |
369 | # This program is free software: you can redistribute it and/or modify it |
370 | # under the terms of the GNU General Public License version 3, as published |
371 | @@ -48,22 +48,6 @@ |
372 | NOT_DEFINED = object() |
373 | |
374 | |
375 | -class FakeWebclient(object): |
376 | - """A fake webclient.""" |
377 | - |
378 | - def __init__(self): |
379 | - self.called = [] |
380 | - |
381 | - def request(self, *args, **kwargs): |
382 | - """Save a webclient request.""" |
383 | - self.called.append((args, kwargs)) |
384 | - response = utils.webclient.common.Response(content="response") |
385 | - return response |
386 | - |
387 | - def shutdown(self): |
388 | - """Shutdown this fake webclient.""" |
389 | - |
390 | - |
391 | class FakedConstantsModule(object): |
392 | """Fake the 'ubuntuone.controlpanel.constants' module.""" |
393 | |
394 | |
395 | === renamed file 'ubuntuone/utils/webclient/tests/test_gsettings.py' => 'ubuntuone/utils/tests/test_gsettings.py' |
396 | --- ubuntuone/utils/webclient/tests/test_gsettings.py 2016-05-29 00:50:05 +0000 |
397 | +++ ubuntuone/utils/tests/test_gsettings.py 2017-01-13 11:56:59 +0000 |
398 | @@ -1,7 +1,7 @@ |
399 | # -*- coding: utf-8 -*- |
400 | # |
401 | # Copyright 2011-2012 Canonical Ltd. |
402 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
403 | +# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros) |
404 | # |
405 | # This program is free software: you can redistribute it and/or modify it |
406 | # under the terms of the GNU General Public License version 3, as published |
407 | @@ -35,11 +35,41 @@ |
408 | from twisted.trial.unittest import TestCase |
409 | from ubuntuone.devtools.handlers import MementoHandler |
410 | |
411 | -from ubuntuone.utils.webclient import gsettings |
412 | -from ubuntuone.utils.webclient.tests import ( |
413 | - BASE_GSETTINGS_VALUES, |
414 | - TEMPLATE_GSETTINGS_OUTPUT, |
415 | -) |
416 | +from ubuntuone.utils import gsettings |
417 | + |
418 | +TEMPLATE_GSETTINGS_OUTPUT = """\ |
419 | +org.gnome.system.proxy autoconfig-url '{autoconfig_url}' |
420 | +org.gnome.system.proxy ignore-hosts {ignore_hosts:s} |
421 | +org.gnome.system.proxy mode '{mode}' |
422 | +org.gnome.system.proxy.ftp host '{ftp_host}' |
423 | +org.gnome.system.proxy.ftp port {ftp_port} |
424 | +org.gnome.system.proxy.http authentication-password '{auth_password}' |
425 | +org.gnome.system.proxy.http authentication-user '{auth_user}' |
426 | +org.gnome.system.proxy.http host '{http_host}' |
427 | +org.gnome.system.proxy.http port {http_port} |
428 | +org.gnome.system.proxy.http use-authentication {http_use_auth} |
429 | +org.gnome.system.proxy.https host '{https_host}' |
430 | +org.gnome.system.proxy.https port {https_port} |
431 | +org.gnome.system.proxy.socks host '{socks_host}' |
432 | +org.gnome.system.proxy.socks port {socks_port} |
433 | +""" |
434 | + |
435 | +BASE_GSETTINGS_VALUES = { |
436 | + "autoconfig_url": "", |
437 | + "ignore_hosts": ["localhost", "127.0.0.0/8"], |
438 | + "mode": "none", |
439 | + "ftp_host": "", |
440 | + "ftp_port": 0, |
441 | + "auth_password": "", |
442 | + "auth_user": "", |
443 | + "http_host": "", |
444 | + "http_port": 0, |
445 | + "http_use_auth": "false", |
446 | + "https_host": "", |
447 | + "https_port": 0, |
448 | + "socks_host": "", |
449 | + "socks_port": 0, |
450 | +} |
451 | |
452 | |
453 | class ProxySettingsTestCase(TestCase): |
454 | |
455 | === removed directory 'ubuntuone/utils/webclient' |
456 | === removed file 'ubuntuone/utils/webclient/__init__.py' |
457 | --- ubuntuone/utils/webclient/__init__.py 2016-06-04 21:14:35 +0000 |
458 | +++ ubuntuone/utils/webclient/__init__.py 1970-01-01 00:00:00 +0000 |
459 | @@ -1,47 +0,0 @@ |
460 | -# -*- coding: utf-8 -*- |
461 | -# |
462 | -# Copyright 2011-2012 Canonical Ltd. |
463 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
464 | -# |
465 | -# This program is free software: you can redistribute it and/or modify it |
466 | -# under the terms of the GNU General Public License version 3, as published |
467 | -# by the Free Software Foundation. |
468 | -# |
469 | -# This program is distributed in the hope that it will be useful, but |
470 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
471 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
472 | -# PURPOSE. See the GNU General Public License for more details. |
473 | -# |
474 | -# You should have received a copy of the GNU General Public License along |
475 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
476 | -# |
477 | -# In addition, as a special exception, the copyright holders give |
478 | -# permission to link the code of portions of this program with the |
479 | -# OpenSSL library under certain conditions as described in each |
480 | -# individual source file, and distribute linked combinations |
481 | -# including the two. |
482 | -# You must obey the GNU General Public License in all respects |
483 | -# for all of the code used other than OpenSSL. If you modify |
484 | -# file(s) with this exception, you may extend this exception to your |
485 | -# version of the file(s), but you are not obligated to do so. If you |
486 | -# do not wish to do so, delete this exception statement from your |
487 | -# version. If you delete this exception statement from all source |
488 | -# files in the program, then also delete it here. |
489 | - |
490 | -"""A common webclient that can use a QtNetwork or libsoup backend.""" |
491 | - |
492 | - |
493 | -def webclient_module(): |
494 | - """Choose the module of the web client.""" |
495 | - if False: |
496 | - from ubuntuone.utils.webclient import txweb |
497 | - return txweb |
498 | - else: |
499 | - from ubuntuone.utils.webclient import libsoup |
500 | - return libsoup |
501 | - |
502 | - |
503 | -def webclient_factory(*args, **kwargs): |
504 | - """Choose the type of the web client dynamically.""" |
505 | - web_module = webclient_module() |
506 | - return web_module.WebClient(*args, **kwargs) |
507 | |
508 | === removed file 'ubuntuone/utils/webclient/common.py' |
509 | --- ubuntuone/utils/webclient/common.py 2016-06-04 21:14:35 +0000 |
510 | +++ ubuntuone/utils/webclient/common.py 1970-01-01 00:00:00 +0000 |
511 | @@ -1,189 +0,0 @@ |
512 | -# -*- coding: utf-8 -*- |
513 | -# |
514 | -# Copyright 2011-2013 Canonical Ltd. |
515 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
516 | -# |
517 | -# This program is free software: you can redistribute it and/or modify it |
518 | -# under the terms of the GNU General Public License version 3, as published |
519 | -# by the Free Software Foundation. |
520 | -# |
521 | -# This program is distributed in the hope that it will be useful, but |
522 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
523 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
524 | -# PURPOSE. See the GNU General Public License for more details. |
525 | -# |
526 | -# You should have received a copy of the GNU General Public License along |
527 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
528 | -# |
529 | -# In addition, as a special exception, the copyright holders give |
530 | -# permission to link the code of portions of this program with the |
531 | -# OpenSSL library under certain conditions as described in each |
532 | -# individual source file, and distribute linked combinations |
533 | -# including the two. |
534 | -# You must obey the GNU General Public License in all respects |
535 | -# for all of the code used other than OpenSSL. If you modify |
536 | -# file(s) with this exception, you may extend this exception to your |
537 | -# version of the file(s), but you are not obligated to do so. If you |
538 | -# do not wish to do so, delete this exception statement from your |
539 | -# version. If you delete this exception statement from all source |
540 | -# files in the program, then also delete it here. |
541 | - |
542 | -"""The common bits of a webclient.""" |
543 | - |
544 | -import collections |
545 | -import logging |
546 | - |
547 | -from httplib2 import iri2uri |
548 | -from twisted.internet import defer |
549 | - |
550 | -from ubuntuone.utils.webclient.timestamp import TimestampChecker |
551 | - |
552 | - |
553 | -logger = logging.getLogger(__name__) |
554 | - |
555 | - |
556 | -class WebClientError(Exception): |
557 | - """An http error happened while calling the webservice.""" |
558 | - |
559 | - |
560 | -class UnauthorizedError(WebClientError): |
561 | - """The request ended with bad_request, unauthorized or forbidden.""" |
562 | - |
563 | - |
564 | -class ProxyUnauthorizedError(WebClientError): |
565 | - """Failure raised when there is an issue with the proxy auth.""" |
566 | - |
567 | - |
568 | -class Response(object): |
569 | - """A response object.""" |
570 | - |
571 | - def __init__(self, content, headers=None): |
572 | - """Initialize this instance.""" |
573 | - self.content = content |
574 | - self.headers = headers |
575 | - |
576 | - |
577 | -class HeaderDict(collections.defaultdict): |
578 | - """A case insensitive dict for headers.""" |
579 | - |
580 | - def __init__(self, *args, **kwargs): |
581 | - """Handle case-insensitive keys.""" |
582 | - super(HeaderDict, self).__init__(list, *args, **kwargs) |
583 | - for key, value in self.items(): |
584 | - super(HeaderDict, self).__delitem__(key) |
585 | - self[key] = value |
586 | - |
587 | - def __setitem__(self, key, value): |
588 | - """Set the value with a case-insensitive key.""" |
589 | - super(HeaderDict, self).__setitem__(key.lower(), value) |
590 | - |
591 | - def __getitem__(self, key): |
592 | - """Get the value with a case-insensitive key.""" |
593 | - return super(HeaderDict, self).__getitem__(key.lower()) |
594 | - |
595 | - def __delitem__(self, key): |
596 | - """Delete the item with the case-insensitive key.""" |
597 | - super(HeaderDict, self).__delitem__(key.lower()) |
598 | - |
599 | - def __contains__(self, key): |
600 | - """Check the containment with a case-insensitive key.""" |
601 | - return super(HeaderDict, self).__contains__(key.lower()) |
602 | - |
603 | - |
604 | -class BaseWebClient(object): |
605 | - """The webclient base class, to be extended by backends.""" |
606 | - |
607 | - timestamp_checker = None |
608 | - |
609 | - def __init__(self, appname='', username=None, password=None): |
610 | - """Initialize this instance.""" |
611 | - self.appname = appname |
612 | - self.username = username |
613 | - self.password = password |
614 | - self.proxy_username = None |
615 | - self.proxy_password = None |
616 | - |
617 | - def request(self, iri, method="GET", extra_headers=None, |
618 | - post_content=None): |
619 | - """Return a deferred that will be fired with a Response object.""" |
620 | - raise NotImplementedError |
621 | - |
622 | - @classmethod |
623 | - def get_timestamp_checker(cls): |
624 | - """Get the timestamp checker for this class of webclient.""" |
625 | - if cls.timestamp_checker is None: |
626 | - cls.timestamp_checker = TimestampChecker(cls) |
627 | - return cls.timestamp_checker |
628 | - |
629 | - def get_timestamp(self): |
630 | - """Get a timestamp synchronized with the server.""" |
631 | - return self.get_timestamp_checker().get_faithful_time() |
632 | - |
633 | - def force_use_proxy(self, settings): |
634 | - """Setup this webclient to use the given proxy settings.""" |
635 | - raise NotImplementedError |
636 | - |
637 | - def iri_to_uri(self, iri): |
638 | - """Transform a unicode iri into a ascii uri.""" |
639 | - if not isinstance(iri, unicode): |
640 | - raise TypeError('iri %r should be unicode.' % iri) |
641 | - return bytes(iri2uri(iri)) |
642 | - |
643 | - def build_auth_request(self, method, uri, credentials, timestamp): |
644 | - """Build an auth request given some credentials.""" |
645 | - # XXX: implement |
646 | - return {'Authorization': 'Auth dummy'} |
647 | - |
648 | - @defer.inlineCallbacks |
649 | - def build_request_headers(self, uri, method="GET", extra_headers=None, |
650 | - auth_credentials=None): |
651 | - """Build the headers for a request.""" |
652 | - if extra_headers: |
653 | - headers = dict(extra_headers) |
654 | - else: |
655 | - headers = {} |
656 | - |
657 | - if auth_credentials: |
658 | - timestamp = yield self.get_timestamp() |
659 | - signed_headers = self.build_auth_request( |
660 | - method, uri, auth_credentials, timestamp) |
661 | - headers.update(signed_headers) |
662 | - |
663 | - defer.returnValue(headers) |
664 | - |
665 | - @defer.inlineCallbacks |
666 | - def build_signed_iri(self, iri, credentials, parameters=None): |
667 | - """Build a new iri signing 'iri' with 'credentials'.""" |
668 | - uri = self.iri_to_uri(iri) |
669 | - timestamp = yield self.get_timestamp() |
670 | - url = self.build_auth_request( |
671 | - method='GET', uri=uri, credentials=credentials, |
672 | - timestamp=timestamp) |
673 | - defer.returnValue(url) |
674 | - |
675 | - def shutdown(self): |
676 | - """Shut down all pending requests (if possible).""" |
677 | - |
678 | - @defer.inlineCallbacks |
679 | - def _load_proxy_creds_from_keyring(self, domain): |
680 | - """Load the proxy creds from the keyring.""" |
681 | - from ubuntuone.keyring import Keyring |
682 | - keyring = Keyring() |
683 | - try: |
684 | - creds = yield keyring.get_credentials(str(domain)) |
685 | - logger.debug('Got credentials from keyring.') |
686 | - except Exception as e: |
687 | - logger.error('Error when retrieving the creds.') |
688 | - raise WebClientError('Error when retrieving the creds.', e) |
689 | - if creds is not None: |
690 | - # if we are loading the same creds it means that we got the wrong |
691 | - # ones |
692 | - if (self.proxy_username == creds['username'] and |
693 | - self.proxy_password == creds['password']): |
694 | - defer.returnValue(False) |
695 | - else: |
696 | - self.proxy_username = creds['username'] |
697 | - self.proxy_password = creds['password'] |
698 | - defer.returnValue(True) |
699 | - logger.debug('Proxy creds not in keyring.') |
700 | - defer.returnValue(False) |
701 | |
702 | === removed file 'ubuntuone/utils/webclient/libsoup.py' |
703 | --- ubuntuone/utils/webclient/libsoup.py 2016-06-04 21:14:35 +0000 |
704 | +++ ubuntuone/utils/webclient/libsoup.py 1970-01-01 00:00:00 +0000 |
705 | @@ -1,137 +0,0 @@ |
706 | -# -*- coding: utf-8 -*- |
707 | -# |
708 | -# Copyright 2011-2012 Canonical Ltd. |
709 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
710 | -# |
711 | -# This program is free software: you can redistribute it and/or modify it |
712 | -# under the terms of the GNU General Public License version 3, as published |
713 | -# by the Free Software Foundation. |
714 | -# |
715 | -# This program is distributed in the hope that it will be useful, but |
716 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
717 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
718 | -# PURPOSE. See the GNU General Public License for more details. |
719 | -# |
720 | -# You should have received a copy of the GNU General Public License along |
721 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
722 | -# |
723 | -# In addition, as a special exception, the copyright holders give |
724 | -# permission to link the code of portions of this program with the |
725 | -# OpenSSL library under certain conditions as described in each |
726 | -# individual source file, and distribute linked combinations |
727 | -# including the two. |
728 | -# You must obey the GNU General Public License in all respects |
729 | -# for all of the code used other than OpenSSL. If you modify |
730 | -# file(s) with this exception, you may extend this exception to your |
731 | -# version of the file(s), but you are not obligated to do so. If you |
732 | -# do not wish to do so, delete this exception statement from your |
733 | -# version. If you delete this exception statement from all source |
734 | -# files in the program, then also delete it here. |
735 | - |
736 | -"""A webclient backend that uses libsoup.""" |
737 | - |
738 | -import httplib |
739 | -import logging |
740 | - |
741 | -from twisted.internet import defer |
742 | - |
743 | -from ubuntuone.utils.webclient.common import ( |
744 | - BaseWebClient, |
745 | - HeaderDict, |
746 | - Response, |
747 | - ProxyUnauthorizedError, |
748 | - UnauthorizedError, |
749 | - WebClientError, |
750 | -) |
751 | - |
752 | -URI_ANONYMOUS_TEMPLATE = "http://{host}:{port}/" |
753 | -URI_USERNAME_TEMPLATE = "http://{username}:{password}@{host}:{port}/" |
754 | - |
755 | -logger = logging.getLogger(__name__) |
756 | - |
757 | - |
758 | -class WebClient(BaseWebClient): |
759 | - """A webclient with a libsoup backend.""" |
760 | - |
761 | - def __init__(self, *args, **kwargs): |
762 | - """Initialize this instance.""" |
763 | - super(WebClient, self).__init__(*args, **kwargs) |
764 | - from gi.repository import Soup, SoupGNOME |
765 | - self.soup = Soup |
766 | - self.session = Soup.SessionAsync() |
767 | - self.session.add_feature(SoupGNOME.ProxyResolverGNOME()) |
768 | - self.session.connect("authenticate", self._on_authenticate) |
769 | - |
770 | - def _on_message(self, session, message, d): |
771 | - """Handle the result of an http message.""" |
772 | - logger.debug('_on_message status code is %s', message.status_code) |
773 | - if message.status_code == httplib.OK: |
774 | - headers = HeaderDict() |
775 | - response_headers = message.get_property("response-headers") |
776 | - response_headers.foreach( |
777 | - lambda key, value, _: headers[key].append(value), None) |
778 | - content = message.response_body.flatten().get_data() |
779 | - response = Response(content, headers) |
780 | - d.callback(response) |
781 | - elif message.status_code == httplib.UNAUTHORIZED: |
782 | - e = UnauthorizedError(message.reason_phrase) |
783 | - d.errback(e) |
784 | - elif message.status_code == httplib.PROXY_AUTHENTICATION_REQUIRED: |
785 | - e = ProxyUnauthorizedError(message.reason_phrase) |
786 | - d.errback(e) |
787 | - else: |
788 | - e = WebClientError(message.reason_phrase) |
789 | - d.errback(e) |
790 | - |
791 | - @defer.inlineCallbacks |
792 | - def _on_authenticate(self, session, message, auth, retrying, data=None): |
793 | - """Handle the "authenticate" signal.""" |
794 | - self.session.pause_message(message) |
795 | - try: |
796 | - logger.debug('_on_authenticate: message status code is %s', |
797 | - message.status_code) |
798 | - if not retrying and self.username and self.password: |
799 | - auth.authenticate(self.username, self.password) |
800 | - finally: |
801 | - self.session.unpause_message(message) |
802 | - |
803 | - @defer.inlineCallbacks |
804 | - def request(self, iri, method="GET", extra_headers=None, |
805 | - auth_credentials=None, post_content=None): |
806 | - """Return a deferred that will be fired with a Response object.""" |
807 | - uri = self.iri_to_uri(iri) |
808 | - headers = yield self.build_request_headers(uri, method, extra_headers, |
809 | - auth_credentials) |
810 | - d = defer.Deferred() |
811 | - message = self.soup.Message.new(method, uri) |
812 | - |
813 | - for key, value in headers.items(): |
814 | - message.request_headers.append(key, value) |
815 | - |
816 | - if post_content: |
817 | - message.request_body.append(post_content) |
818 | - |
819 | - self.session.queue_message(message, self._on_message, d) |
820 | - response = yield d |
821 | - defer.returnValue(response) |
822 | - |
823 | - def force_use_proxy(self, settings): |
824 | - """Setup this webclient to use the given proxy settings.""" |
825 | - proxy_uri = self.get_proxy_uri(settings) |
826 | - self.session.set_property("proxy-uri", proxy_uri) |
827 | - |
828 | - def get_proxy_uri(self, settings): |
829 | - """Get a Soup.URI for the proxy, or None if disabled.""" |
830 | - if "host" in settings and "port" in settings: |
831 | - template = URI_ANONYMOUS_TEMPLATE |
832 | - if "username" in settings and "password" in settings: |
833 | - template = URI_USERNAME_TEMPLATE |
834 | - uri = template.format(**settings) |
835 | - return self.soup.URI.new(uri) |
836 | - else: |
837 | - # If the proxy host is not set, use no proxy |
838 | - return None |
839 | - |
840 | - def shutdown(self): |
841 | - """End the soup session for this webclient.""" |
842 | - self.session.abort() |
843 | |
844 | === removed file 'ubuntuone/utils/webclient/restful.py' |
845 | --- ubuntuone/utils/webclient/restful.py 2016-05-29 00:50:05 +0000 |
846 | +++ ubuntuone/utils/webclient/restful.py 1970-01-01 00:00:00 +0000 |
847 | @@ -1,94 +0,0 @@ |
848 | -# -*- coding: utf-8 -*- |
849 | -# |
850 | -# Copyright 2011-2012 Canonical Ltd. |
851 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
852 | -# |
853 | -# This program is free software: you can redistribute it and/or modify it |
854 | -# under the terms of the GNU General Public License version 3, as published |
855 | -# by the Free Software Foundation. |
856 | -# |
857 | -# This program is distributed in the hope that it will be useful, but |
858 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
859 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
860 | -# PURPOSE. See the GNU General Public License for more details. |
861 | -# |
862 | -# You should have received a copy of the GNU General Public License along |
863 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
864 | -# |
865 | -# In addition, as a special exception, the copyright holders give |
866 | -# permission to link the code of portions of this program with the |
867 | -# OpenSSL library under certain conditions as described in each |
868 | -# individual source file, and distribute linked combinations |
869 | -# including the two. |
870 | -# You must obey the GNU General Public License in all respects |
871 | -# for all of the code used other than OpenSSL. If you modify |
872 | -# file(s) with this exception, you may extend this exception to your |
873 | -# version of the file(s), but you are not obligated to do so. If you |
874 | -# do not wish to do so, delete this exception statement from your |
875 | -# version. If you delete this exception statement from all source |
876 | -# files in the program, then also delete it here. |
877 | - |
878 | -"""A proxy-enabled restful client.""" |
879 | - |
880 | -import json |
881 | -import logging |
882 | - |
883 | -try: |
884 | - from urllib.parse import urlencode |
885 | -except ImportError: |
886 | - from urllib import urlencode |
887 | - |
888 | -from twisted.internet import defer |
889 | - |
890 | -from ubuntuone.utils import webclient |
891 | - |
892 | -logger = logging.getLogger(__name__) |
893 | - |
894 | -POST_HEADERS = { |
895 | - "content-type": "application/x-www-form-urlencoded", |
896 | -} |
897 | - |
898 | - |
899 | -class RestfulClient(object): |
900 | - """A proxy-enabled restful client.""" |
901 | - |
902 | - def __init__(self, service_iri, username=None, password=None, |
903 | - auth_credentials=None): |
904 | - """Initialize this instance.""" |
905 | - assert service_iri.endswith("/") |
906 | - self.service_iri = service_iri |
907 | - self.webclient = webclient.webclient_factory(username=username, |
908 | - password=password) |
909 | - self.auth_credentials = auth_credentials |
910 | - |
911 | - @defer.inlineCallbacks |
912 | - def restcall(self, method, **kwargs): |
913 | - """Make a restful call.""" |
914 | - assert isinstance(method, unicode) |
915 | - params = {} |
916 | - for key, value in kwargs.items(): |
917 | - if isinstance(value, basestring): |
918 | - assert isinstance(value, unicode) |
919 | - params[key] = json.dumps(value) |
920 | - namespace, operation = method.split(".") |
921 | - params["ws.op"] = operation |
922 | - encoded_args = urlencode(params) |
923 | - iri = self.service_iri + namespace |
924 | - creds = self.auth_credentials |
925 | - logger.debug('Performing REST call to %r.', iri) |
926 | - result = yield self.webclient.request(iri, method="POST", |
927 | - auth_credentials=creds, |
928 | - post_content=encoded_args, |
929 | - extra_headers=POST_HEADERS) |
930 | - try: |
931 | - response = json.loads(result.content) |
932 | - except: |
933 | - logger.exception('Can not load json from REST request response ' |
934 | - '(content is %r).', result.content) |
935 | - raise |
936 | - else: |
937 | - defer.returnValue(response) |
938 | - |
939 | - def shutdown(self): |
940 | - """Stop the webclient used by this class.""" |
941 | - self.webclient.shutdown() |
942 | |
943 | === removed directory 'ubuntuone/utils/webclient/tests' |
944 | === removed file 'ubuntuone/utils/webclient/tests/__init__.py' |
945 | --- ubuntuone/utils/webclient/tests/__init__.py 2016-05-28 23:52:12 +0000 |
946 | +++ ubuntuone/utils/webclient/tests/__init__.py 1970-01-01 00:00:00 +0000 |
947 | @@ -1,65 +0,0 @@ |
948 | -# -*- coding: utf-8 -*- |
949 | -# |
950 | -# Copyright 2011-2012 Canonical Ltd. |
951 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
952 | -# |
953 | -# This program is free software: you can redistribute it and/or modify it |
954 | -# under the terms of the GNU General Public License version 3, as published |
955 | -# by the Free Software Foundation. |
956 | -# |
957 | -# This program is distributed in the hope that it will be useful, but |
958 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
959 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
960 | -# PURPOSE. See the GNU General Public License for more details. |
961 | -# |
962 | -# You should have received a copy of the GNU General Public License along |
963 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
964 | -# |
965 | -# In addition, as a special exception, the copyright holders give |
966 | -# permission to link the code of portions of this program with the |
967 | -# OpenSSL library under certain conditions as described in each |
968 | -# individual source file, and distribute linked combinations |
969 | -# including the two. |
970 | -# You must obey the GNU General Public License in all respects |
971 | -# for all of the code used other than OpenSSL. If you modify |
972 | -# file(s) with this exception, you may extend this exception to your |
973 | -# version of the file(s), but you are not obligated to do so. If you |
974 | -# do not wish to do so, delete this exception statement from your |
975 | -# version. If you delete this exception statement from all source |
976 | -# files in the program, then also delete it here. |
977 | - |
978 | -"""Tests for the proxy-aware webclient.""" |
979 | - |
980 | -TEMPLATE_GSETTINGS_OUTPUT = """\ |
981 | -org.gnome.system.proxy autoconfig-url '{autoconfig_url}' |
982 | -org.gnome.system.proxy ignore-hosts {ignore_hosts:s} |
983 | -org.gnome.system.proxy mode '{mode}' |
984 | -org.gnome.system.proxy.ftp host '{ftp_host}' |
985 | -org.gnome.system.proxy.ftp port {ftp_port} |
986 | -org.gnome.system.proxy.http authentication-password '{auth_password}' |
987 | -org.gnome.system.proxy.http authentication-user '{auth_user}' |
988 | -org.gnome.system.proxy.http host '{http_host}' |
989 | -org.gnome.system.proxy.http port {http_port} |
990 | -org.gnome.system.proxy.http use-authentication {http_use_auth} |
991 | -org.gnome.system.proxy.https host '{https_host}' |
992 | -org.gnome.system.proxy.https port {https_port} |
993 | -org.gnome.system.proxy.socks host '{socks_host}' |
994 | -org.gnome.system.proxy.socks port {socks_port} |
995 | -""" |
996 | - |
997 | -BASE_GSETTINGS_VALUES = { |
998 | - "autoconfig_url": "", |
999 | - "ignore_hosts": ["localhost", "127.0.0.0/8"], |
1000 | - "mode": "none", |
1001 | - "ftp_host": "", |
1002 | - "ftp_port": 0, |
1003 | - "auth_password": "", |
1004 | - "auth_user": "", |
1005 | - "http_host": "", |
1006 | - "http_port": 0, |
1007 | - "http_use_auth": "false", |
1008 | - "https_host": "", |
1009 | - "https_port": 0, |
1010 | - "socks_host": "", |
1011 | - "socks_port": 0, |
1012 | -} |
1013 | |
1014 | === removed file 'ubuntuone/utils/webclient/tests/test_restful.py' |
1015 | --- ubuntuone/utils/webclient/tests/test_restful.py 2016-05-29 00:50:05 +0000 |
1016 | +++ ubuntuone/utils/webclient/tests/test_restful.py 1970-01-01 00:00:00 +0000 |
1017 | @@ -1,233 +0,0 @@ |
1018 | -# -*- coding: utf-8 -*- |
1019 | -# |
1020 | -# Copyright 2011-2012 Canonical Ltd. |
1021 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
1022 | -# |
1023 | -# This program is free software: you can redistribute it and/or modify it |
1024 | -# under the terms of the GNU General Public License version 3, as published |
1025 | -# by the Free Software Foundation. |
1026 | -# |
1027 | -# This program is distributed in the hope that it will be useful, but |
1028 | -# WITHOUT AN WARRANTY; without even the implied warranties of |
1029 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1030 | -# PURPOSE. See the GNU General Public License for more details. |
1031 | -# |
1032 | -# You should have received a copy of the GNU General Public License along |
1033 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1034 | -# |
1035 | -# In addition, as a special exception, the copyright holders give |
1036 | -# permission to link the code of portions of this program with the |
1037 | -# OpenSSL library under certain conditions as described in each |
1038 | -# individual source file, and distribute linked combinations |
1039 | -# including the two. |
1040 | -# You must obey the GNU General Public License in all respects |
1041 | -# for all of the code used other than OpenSSL. If you modify |
1042 | -# file(s) with this exception, you may extend this exception to your |
1043 | -# version of the file(s), but you are not obligated to do so. If you |
1044 | -# do not wish to do so, delete this exception statement from your |
1045 | -# version. If you delete this exception statement from all source |
1046 | -# files in the program, then also delete it here. |
1047 | - |
1048 | -"""Tests for the proxy-enabled restful client.""" |
1049 | - |
1050 | -import logging |
1051 | - |
1052 | -try: |
1053 | - from urllib.parse import urlparse, parse_qs, parse_qsl |
1054 | -except ImportError: |
1055 | - from urlparse import urlparse, parse_qs, parse_qsl |
1056 | - |
1057 | -from twisted.internet import defer |
1058 | -from ubuntuone.devtools.handlers import MementoHandler |
1059 | -from ubuntuone.devtools.testcases import TestCase |
1060 | - |
1061 | -from ubuntuone.utils.webclient import restful |
1062 | -from ubuntuone.utils.webclient.common import Response |
1063 | - |
1064 | - |
1065 | -SAMPLE_SERVICE_IRI = u"http://localhost/" |
1066 | -SAMPLE_NAMESPACE = u"sample_namespace" |
1067 | -SAMPLE_METHOD = u"sample_method" |
1068 | -SAMPLE_OPERATION = SAMPLE_NAMESPACE + u"." + SAMPLE_METHOD |
1069 | -SAMPLE_ARGS = dict(uno=1, dos=u"2", tres=u"ñandú") |
1070 | -SAMPLE_RESPONSE = restful.json.dumps(SAMPLE_ARGS) |
1071 | -SAMPLE_USERNAME = "joeuser@example.com" |
1072 | -SAMPLE_PASSWORD = "clavesecreta" |
1073 | -SAMPLE_AUTH_CREDS = dict(token="1234", etc="456") |
1074 | - |
1075 | - |
1076 | -class FakeWebClient(object): |
1077 | - """A fake web client.""" |
1078 | - |
1079 | - def __init__(self, **kwargs): |
1080 | - """Initialize this faker.""" |
1081 | - self.return_value = SAMPLE_RESPONSE |
1082 | - self.called = [] |
1083 | - self.init_kwargs = kwargs |
1084 | - self.running = True |
1085 | - |
1086 | - def request(self, iri, *args, **kwargs): |
1087 | - """Return a deferred that will be fired with a Response object.""" |
1088 | - self.called.append((iri, args, kwargs)) |
1089 | - return defer.succeed(Response(self.return_value)) |
1090 | - |
1091 | - def shutdown(self): |
1092 | - """Stop this fake webclient.""" |
1093 | - self.running = False |
1094 | - |
1095 | - |
1096 | -class BaseTestCase(TestCase): |
1097 | - """The base for the Restful Client testcases.""" |
1098 | - |
1099 | - @defer.inlineCallbacks |
1100 | - def setUp(self): |
1101 | - """Initialize this test case.""" |
1102 | - yield super(BaseTestCase, self).setUp() |
1103 | - self.wc = None |
1104 | - self.patch(restful.webclient, "webclient_factory", |
1105 | - self.webclient_factory) |
1106 | - |
1107 | - def webclient_factory(self, **kwargs): |
1108 | - """A factory that saves the webclient created.""" |
1109 | - self.wc = FakeWebClient(**kwargs) |
1110 | - return self.wc |
1111 | - |
1112 | - |
1113 | -class RestfulClientTestCase(BaseTestCase): |
1114 | - """Tests for the proxy-enabled Restful Client.""" |
1115 | - |
1116 | - @defer.inlineCallbacks |
1117 | - def setUp(self): |
1118 | - """Initialize this testcase.""" |
1119 | - yield super(RestfulClientTestCase, self).setUp() |
1120 | - self.rc = restful.RestfulClient(SAMPLE_SERVICE_IRI) |
1121 | - self.addCleanup(self.rc.shutdown) |
1122 | - |
1123 | - def test_has_a_webclient(self): |
1124 | - """The RC has a webclient.""" |
1125 | - self.assertEqual(self.rc.webclient, self.wc) |
1126 | - |
1127 | - def test_shutsdown_the_webclient(self): |
1128 | - """Calling shutdown on the restful shuts down the webclient too.""" |
1129 | - self.rc.shutdown() |
1130 | - self.assertFalse(self.rc.webclient.running, "The webclient is stopped") |
1131 | - |
1132 | - @defer.inlineCallbacks |
1133 | - def test_can_make_calls(self): |
1134 | - """The RC can make webcalls.""" |
1135 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1136 | - self.assertEqual(len(self.wc.called), 1) |
1137 | - |
1138 | - @defer.inlineCallbacks |
1139 | - def test_restful_namespace_added_to_url(self): |
1140 | - """The restful namespace is added to the url.""" |
1141 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1142 | - iri, _, _ = self.wc.called[0] |
1143 | - uri = iri.encode("ascii") |
1144 | - url = urlparse(uri) |
1145 | - self.assertTrue(url.path.endswith(SAMPLE_NAMESPACE), |
1146 | - "The namespace is included in url") |
1147 | - |
1148 | - @defer.inlineCallbacks |
1149 | - def test_restful_method_added_to_params(self): |
1150 | - """The restful method is added to the params.""" |
1151 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1152 | - _, _, webcall_kwargs = self.wc.called[0] |
1153 | - wc_params = parse_qs(webcall_kwargs["post_content"]) |
1154 | - self.assertEqual(wc_params["ws.op"][0], SAMPLE_METHOD) |
1155 | - |
1156 | - @defer.inlineCallbacks |
1157 | - def test_arguments_added_as_json_to_webcall(self): |
1158 | - """The keyword arguments are used as json in the webcall.""" |
1159 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1160 | - _, _, webcall_kwargs = self.wc.called[0] |
1161 | - params = parse_qsl(webcall_kwargs["post_content"]) |
1162 | - result = {} |
1163 | - for key, value in params: |
1164 | - if key == "ws.op": |
1165 | - continue |
1166 | - result[key] = restful.json.loads(value) |
1167 | - self.assertEqual(result, SAMPLE_ARGS) |
1168 | - |
1169 | - @defer.inlineCallbacks |
1170 | - def test_post_header_sent(self): |
1171 | - """A header is sent specifying the contents of the post.""" |
1172 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1173 | - _, _, webcall_kwargs = self.wc.called[0] |
1174 | - self.assertEqual(restful.POST_HEADERS, |
1175 | - webcall_kwargs["extra_headers"]) |
1176 | - |
1177 | - @defer.inlineCallbacks |
1178 | - def test_post_method_set(self): |
1179 | - """The method of the webcall is set to POST.""" |
1180 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1181 | - _, _, webcall_kwargs = self.wc.called[0] |
1182 | - self.assertEqual("POST", webcall_kwargs["method"]) |
1183 | - |
1184 | - @defer.inlineCallbacks |
1185 | - def test_return_value_json_parsed(self): |
1186 | - """The result is json parsed before being returned.""" |
1187 | - result = yield self.rc.restcall(SAMPLE_OPERATION) |
1188 | - self.assertEqual(result, SAMPLE_ARGS) |
1189 | - |
1190 | - |
1191 | -class AuthenticationOptionsTestCase(BaseTestCase): |
1192 | - """Tests for the authentication options.""" |
1193 | - |
1194 | - def test_passes_userpass_to_webclient_init(self): |
1195 | - """The RestfulClient passes the user and pass to the webclient.""" |
1196 | - params = dict(username=SAMPLE_USERNAME, password=SAMPLE_PASSWORD) |
1197 | - restful.RestfulClient(SAMPLE_SERVICE_IRI, **params) |
1198 | - expected = dict(params) |
1199 | - self.assertEqual(self.wc.init_kwargs, expected) |
1200 | - |
1201 | - @defer.inlineCallbacks |
1202 | - def test_passes_auth_creds_to_request(self): |
1203 | - """The RestfulClient passes the credentials in each request.""" |
1204 | - kwargs = dict(auth_credentials=SAMPLE_AUTH_CREDS) |
1205 | - rc = restful.RestfulClient(SAMPLE_SERVICE_IRI, **kwargs) |
1206 | - yield rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1207 | - _, _, kwargs = self.wc.called[0] |
1208 | - self.assertEqual(kwargs["auth_credentials"], SAMPLE_AUTH_CREDS) |
1209 | - |
1210 | - |
1211 | -class LogginTestCase(BaseTestCase): |
1212 | - """Ensure that proper debug logging is done.""" |
1213 | - |
1214 | - @defer.inlineCallbacks |
1215 | - def setUp(self): |
1216 | - """Initialize this testcase.""" |
1217 | - yield super(LogginTestCase, self).setUp() |
1218 | - self.memento = MementoHandler() |
1219 | - restful.logger.addHandler(self.memento) |
1220 | - restful.logger.setLevel(logging.DEBUG) |
1221 | - self.addCleanup(restful.logger.removeHandler, self.memento) |
1222 | - |
1223 | - self.rc = restful.RestfulClient(SAMPLE_SERVICE_IRI) |
1224 | - self.addCleanup(self.rc.shutdown) |
1225 | - |
1226 | - @defer.inlineCallbacks |
1227 | - def test_log_rest_call(self): |
1228 | - """Check that proper DEBUG is made for every REST call.""" |
1229 | - yield self.rc.restcall(SAMPLE_OPERATION, **SAMPLE_ARGS) |
1230 | - |
1231 | - expected_msgs = ( |
1232 | - SAMPLE_SERVICE_IRI + SAMPLE_NAMESPACE, |
1233 | - ) |
1234 | - self.assertTrue(self.memento.check_debug(*expected_msgs)) |
1235 | - |
1236 | - @defer.inlineCallbacks |
1237 | - def test_log_json_loads_exception(self): |
1238 | - """Check that json load errors are properly logged.""" |
1239 | - invalid_json = 'NOTAVALIDJSON' |
1240 | - self.patch(self.wc, 'return_value', invalid_json) |
1241 | - yield self.assertFailure(self.rc.restcall(SAMPLE_OPERATION), |
1242 | - ValueError) |
1243 | - |
1244 | - self.memento.debug = True |
1245 | - expected_msgs = ( |
1246 | - ValueError, |
1247 | - 'Can not load json from REST request response', |
1248 | - invalid_json |
1249 | - ) |
1250 | - self.assertTrue(self.memento.check_exception(*expected_msgs)) |
1251 | |
1252 | === removed file 'ubuntuone/utils/webclient/tests/test_timestamp.py' |
1253 | --- ubuntuone/utils/webclient/tests/test_timestamp.py 2016-06-04 21:14:35 +0000 |
1254 | +++ ubuntuone/utils/webclient/tests/test_timestamp.py 1970-01-01 00:00:00 +0000 |
1255 | @@ -1,141 +0,0 @@ |
1256 | -# -*- coding: utf-8 -*- |
1257 | -# |
1258 | -# Copyright 2011-2012 Canonical Ltd. |
1259 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
1260 | -# |
1261 | -# This program is free software: you can redistribute it and/or modify it |
1262 | -# under the terms of the GNU General Public License version 3, as published |
1263 | -# by the Free Software Foundation. |
1264 | -# |
1265 | -# This program is distributed in the hope that it will be useful, but |
1266 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
1267 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1268 | -# PURPOSE. See the GNU General Public License for more details. |
1269 | -# |
1270 | -# You should have received a copy of the GNU General Public License along |
1271 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1272 | -# |
1273 | -# In addition, as a special exception, the copyright holders give |
1274 | -# permission to link the code of portions of this program with the |
1275 | -# OpenSSL library under certain conditions as described in each |
1276 | -# individual source file, and distribute linked combinations |
1277 | -# including the two. |
1278 | -# You must obey the GNU General Public License in all respects |
1279 | -# for all of the code used other than OpenSSL. If you modify |
1280 | -# file(s) with this exception, you may extend this exception to your |
1281 | -# version of the file(s), but you are not obligated to do so. If you |
1282 | -# do not wish to do so, delete this exception statement from your |
1283 | -# version. If you delete this exception statement from all source |
1284 | -# files in the program, then also delete it here. |
1285 | - |
1286 | -"""Tests for the timestamp sync classes.""" |
1287 | - |
1288 | -from twisted.internet import defer |
1289 | -from twisted.trial.unittest import TestCase |
1290 | -from twisted.web import resource |
1291 | - |
1292 | -from ubuntuone.devtools.testing.txwebserver import HTTPWebServer |
1293 | - |
1294 | -from ubuntuone.utils.webclient import timestamp, webclient_module |
1295 | - |
1296 | - |
1297 | -class FakedError(Exception): |
1298 | - """Stub to replace Request.error.""" |
1299 | - |
1300 | - |
1301 | -class RootResource(resource.Resource): |
1302 | - """A root resource that logs the number of calls.""" |
1303 | - |
1304 | - isLeaf = True |
1305 | - |
1306 | - def __init__(self, *args, **kwargs): |
1307 | - """Initialize this fake instance.""" |
1308 | - resource.Resource.__init__(self, *args, **kwargs) |
1309 | - self.count = 0 |
1310 | - self.request_headers = [] |
1311 | - |
1312 | - def render_HEAD(self, request): |
1313 | - """Increase the counter on each render.""" |
1314 | - self.request_headers.append(request.requestHeaders) |
1315 | - self.count += 1 |
1316 | - return "" |
1317 | - |
1318 | - |
1319 | -class MockWebServer(HTTPWebServer): |
1320 | - """A mock webserver for testing.""" |
1321 | - |
1322 | - def __init__(self): |
1323 | - """Create a new server.""" |
1324 | - super(MockWebServer, self).__init__(RootResource()) |
1325 | - |
1326 | - |
1327 | -class TimestampCheckerTestCase(TestCase): |
1328 | - """Tests for the timestamp checker.""" |
1329 | - |
1330 | - timeout = 5 |
1331 | - |
1332 | - @defer.inlineCallbacks |
1333 | - def setUp(self): |
1334 | - yield super(TimestampCheckerTestCase, self).setUp() |
1335 | - self.ws = MockWebServer() |
1336 | - self.ws.start() |
1337 | - self.addCleanup(self.ws.stop) |
1338 | - self.webclient_class = webclient_module().WebClient |
1339 | - self.patch(timestamp.TimestampChecker, "SERVER_IRI", self.ws.get_iri()) |
1340 | - |
1341 | - @defer.inlineCallbacks |
1342 | - def test_returned_value_is_int(self): |
1343 | - """The returned value is an integer.""" |
1344 | - checker = timestamp.TimestampChecker(self.webclient_class) |
1345 | - result = yield checker.get_faithful_time() |
1346 | - self.assertEqual(type(result), int) |
1347 | - |
1348 | - @defer.inlineCallbacks |
1349 | - def test_first_call_does_head(self): |
1350 | - """The first call gets the clock from our web.""" |
1351 | - checker = timestamp.TimestampChecker(self.webclient_class) |
1352 | - yield checker.get_faithful_time() |
1353 | - self.assertEqual(self.ws.root.count, 1) |
1354 | - |
1355 | - @defer.inlineCallbacks |
1356 | - def test_second_call_is_cached(self): |
1357 | - """For the second call, the time is cached.""" |
1358 | - checker = timestamp.TimestampChecker(self.webclient_class) |
1359 | - yield checker.get_faithful_time() |
1360 | - yield checker.get_faithful_time() |
1361 | - self.assertEqual(self.ws.root.count, 1) |
1362 | - |
1363 | - @defer.inlineCallbacks |
1364 | - def test_after_timeout_cache_expires(self): |
1365 | - """After some time, the cache expires.""" |
1366 | - fake_timestamp = 1 |
1367 | - self.patch(timestamp.time, "time", lambda: fake_timestamp) |
1368 | - checker = timestamp.TimestampChecker(self.webclient_class) |
1369 | - yield checker.get_faithful_time() |
1370 | - fake_timestamp += timestamp.TimestampChecker.CHECKING_INTERVAL |
1371 | - yield checker.get_faithful_time() |
1372 | - self.assertEqual(self.ws.root.count, 2) |
1373 | - |
1374 | - @defer.inlineCallbacks |
1375 | - def test_server_error_means_skew_not_updated(self): |
1376 | - """When server can't be reached, the skew is not updated.""" |
1377 | - fake_timestamp = 1 |
1378 | - self.patch(timestamp.time, "time", lambda: fake_timestamp) |
1379 | - checker = timestamp.TimestampChecker(self.webclient_class) |
1380 | - self.patch( |
1381 | - checker, "get_server_time", lambda _: defer.fail(FakedError())) |
1382 | - yield checker.get_faithful_time() |
1383 | - self.assertEqual(checker.skew, 0) |
1384 | - self.assertEqual( |
1385 | - checker.next_check, |
1386 | - fake_timestamp + timestamp.TimestampChecker.ERROR_INTERVAL) |
1387 | - |
1388 | - @defer.inlineCallbacks |
1389 | - def test_server_date_sends_nocache_headers(self): |
1390 | - """Getting the server date sends the no-cache headers.""" |
1391 | - checker = timestamp.TimestampChecker(self.webclient_class) |
1392 | - yield checker.get_server_date_header(self.ws.get_iri()) |
1393 | - self.assertEqual(len(self.ws.root.request_headers), 1) |
1394 | - headers = self.ws.root.request_headers[0] |
1395 | - result = headers.getRawHeaders("Cache-Control") |
1396 | - self.assertEqual(result, ["no-cache"]) |
1397 | |
1398 | === removed file 'ubuntuone/utils/webclient/tests/test_webclient.py' |
1399 | --- ubuntuone/utils/webclient/tests/test_webclient.py 2016-06-04 21:14:35 +0000 |
1400 | +++ ubuntuone/utils/webclient/tests/test_webclient.py 1970-01-01 00:00:00 +0000 |
1401 | @@ -1,753 +0,0 @@ |
1402 | -# -*- coding: utf-8 -*- |
1403 | -# |
1404 | -# Copyright 2011-2013 Canonical Ltd. |
1405 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
1406 | -# |
1407 | -# This program is free software: you can redistribute it and/or modify it |
1408 | -# under the terms of the GNU General Public License version 3, as published |
1409 | -# by the Free Software Foundation. |
1410 | -# |
1411 | -# This program is distributed in the hope that it will be useful, but |
1412 | -# WITHOUT AN WARRANTY; without even the implied warranties of |
1413 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1414 | -# PURPOSE. See the GNU General Public License for more details. |
1415 | -# |
1416 | -# You should have received a copy of the GNU General Public License along |
1417 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1418 | -# |
1419 | -# In addition, as a special exception, the copyright holders give |
1420 | -# permission to link the code of portions of this program with the |
1421 | -# OpenSSL library under certain conditions as described in each |
1422 | -# individual source file, and distribute linked combinations |
1423 | -# including the two. |
1424 | -# You must obey the GNU General Public License in all respects |
1425 | -# for all of the code used other than OpenSSL. If you modify |
1426 | -# file(s) with this exception, you may extend this exception to your |
1427 | -# version of the file(s), but you are not obligated to do so. If you |
1428 | -# do not wish to do so, delete this exception statement from your |
1429 | -# version. If you delete this exception statement from all source |
1430 | -# files in the program, then also delete it here. |
1431 | - |
1432 | -"""Integration tests for the proxy-enabled webclient.""" |
1433 | - |
1434 | -import logging |
1435 | -import os |
1436 | -import shutil |
1437 | - |
1438 | -try: |
1439 | - from urllib.parse import urlencode |
1440 | -except ImportError: |
1441 | - from urllib import urlencode |
1442 | - |
1443 | -from OpenSSL import crypto |
1444 | -from socket import gethostname |
1445 | -from twisted.cred import checkers, portal |
1446 | -from twisted.internet import defer |
1447 | -from twisted.web import guard, http, resource |
1448 | -from ubuntuone.devtools.handlers import MementoHandler |
1449 | -from ubuntuone.devtools.testcases import TestCase, skipIfOS |
1450 | -from ubuntuone.devtools.testcases.squid import SquidTestCase |
1451 | -from ubuntuone.devtools.testing.txwebserver import ( |
1452 | - HTTPWebServer, |
1453 | - HTTPSWebServer, |
1454 | -) |
1455 | - |
1456 | -from ubuntuone.utils import webclient |
1457 | -from ubuntuone.utils.webclient import gsettings, txweb |
1458 | -from ubuntuone.utils.webclient.common import ( |
1459 | - HeaderDict, |
1460 | - UnauthorizedError, |
1461 | - WebClientError, |
1462 | -) |
1463 | - |
1464 | -ANY_VALUE = object() |
1465 | -SAMPLE_KEY = "result" |
1466 | -SAMPLE_VALUE = "sample result" |
1467 | -SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE) |
1468 | -SAMPLE_USERNAME = "peddro" |
1469 | -SAMPLE_PASSWORD = "cantropus" |
1470 | -SAMPLE_CREDENTIALS = dict(username="username", password="password") |
1471 | -SAMPLE_HEADERS = {SAMPLE_KEY: SAMPLE_VALUE} |
1472 | -SAMPLE_POST_PARAMS = {"param1": "value1", "param2": "value2"} |
1473 | -SAMPLE_JPEG_HEADER = '\xff\xd8\xff\xe0\x00\x10JFIF' |
1474 | - |
1475 | -SIMPLERESOURCE = "simpleresource" |
1476 | -BYTEZERORESOURCE = "bytezeroresource" |
1477 | -POSTABLERESOURCE = "postableresource" |
1478 | -THROWERROR = "throwerror" |
1479 | -UNAUTHORIZED = "unauthorized" |
1480 | -HEADONLY = "headonly" |
1481 | -VERIFYHEADERS = "verifyheaders" |
1482 | -VERIFYPOSTPARAMS = "verifypostparams" |
1483 | -GUARDED = "guarded" |
1484 | -AUTHRESOURCE = "authresource" |
1485 | - |
1486 | -WEBCLIENT_MODULE_NAME = webclient.webclient_module().__name__ |
1487 | - |
1488 | - |
1489 | -def sample_get_credentials(): |
1490 | - """Will return the sample credentials right now.""" |
1491 | - return defer.succeed(SAMPLE_CREDENTIALS) |
1492 | - |
1493 | - |
1494 | -class SimpleResource(resource.Resource): |
1495 | - """A simple web resource.""" |
1496 | - |
1497 | - def render_GET(self, request): |
1498 | - """Make a bit of html out of these resource's content.""" |
1499 | - return SAMPLE_RESOURCE |
1500 | - |
1501 | - |
1502 | -class ByteZeroResource(resource.Resource): |
1503 | - """A resource that has a nul byte in the middle of it.""" |
1504 | - |
1505 | - def render_GET(self, request): |
1506 | - """Return the content of this resource.""" |
1507 | - return SAMPLE_JPEG_HEADER |
1508 | - |
1509 | - |
1510 | -class PostableResource(resource.Resource): |
1511 | - """A resource that only answers to POST requests.""" |
1512 | - |
1513 | - def render_POST(self, request): |
1514 | - """Make a bit of html out of these resource's content.""" |
1515 | - return SAMPLE_RESOURCE |
1516 | - |
1517 | - |
1518 | -class HeadOnlyResource(resource.Resource): |
1519 | - """A resource that fails if called with a method other than HEAD.""" |
1520 | - |
1521 | - def render_HEAD(self, request): |
1522 | - """Return a bit of html.""" |
1523 | - return "OK" |
1524 | - |
1525 | - |
1526 | -class VerifyHeadersResource(resource.Resource): |
1527 | - """A resource that verifies the headers received.""" |
1528 | - |
1529 | - def render_GET(self, request): |
1530 | - """Make a bit of html out of these resource's content.""" |
1531 | - headers = request.requestHeaders.getRawHeaders(SAMPLE_KEY) |
1532 | - if headers != [SAMPLE_VALUE]: |
1533 | - request.setResponseCode(http.BAD_REQUEST) |
1534 | - return "ERROR: Expected header not present." |
1535 | - request.setHeader(SAMPLE_KEY, SAMPLE_VALUE) |
1536 | - return SAMPLE_RESOURCE |
1537 | - |
1538 | - |
1539 | -class VerifyPostParameters(resource.Resource): |
1540 | - """A resource that answers to POST requests with some parameters.""" |
1541 | - |
1542 | - def fetch_post_args_only(self, request): |
1543 | - """Fetch only the POST arguments, not the args in the url.""" |
1544 | - request.process = lambda: None |
1545 | - request.requestReceived(request.method, request.path, |
1546 | - request.clientproto) |
1547 | - return request.args |
1548 | - |
1549 | - def render_POST(self, request): |
1550 | - """Verify the parameters that we've been called with.""" |
1551 | - post_params = self.fetch_post_args_only(request) |
1552 | - expected = dict( |
1553 | - (key, [val]) for key, val in SAMPLE_POST_PARAMS.items()) |
1554 | - if post_params != expected: |
1555 | - request.setResponseCode(http.BAD_REQUEST) |
1556 | - return "ERROR: Expected arguments not present, %r != %r" % ( |
1557 | - post_params, expected) |
1558 | - return SAMPLE_RESOURCE |
1559 | - |
1560 | - |
1561 | -class SimpleRealm(object): |
1562 | - """The same simple resource for all users.""" |
1563 | - |
1564 | - def requestAvatar(self, avatarId, mind, *interfaces): |
1565 | - """The avatar for this user.""" |
1566 | - if resource.IResource in interfaces: |
1567 | - return (resource.IResource, SimpleResource(), lambda: None) |
1568 | - raise NotImplementedError() |
1569 | - |
1570 | - |
1571 | -class AuthCheckerResource(resource.Resource): |
1572 | - """A resource that verifies the request was auth signed.""" |
1573 | - |
1574 | - def render_GET(self, request): |
1575 | - """Make a bit of html out of these resource's content.""" |
1576 | - header = request.requestHeaders.getRawHeaders("Authorization")[0] |
1577 | - if header.startswith("Auth "): |
1578 | - return SAMPLE_RESOURCE |
1579 | - request.setResponseCode(http.BAD_REQUEST) |
1580 | - return "ERROR: Expected Auth header not present." |
1581 | - |
1582 | - |
1583 | -def get_root_resource(): |
1584 | - """Get the root resource with all the children.""" |
1585 | - root = resource.Resource() |
1586 | - root.putChild(SIMPLERESOURCE, SimpleResource()) |
1587 | - root.putChild(BYTEZERORESOURCE, ByteZeroResource()) |
1588 | - root.putChild(POSTABLERESOURCE, PostableResource()) |
1589 | - |
1590 | - root.putChild(THROWERROR, resource.NoResource()) |
1591 | - |
1592 | - unauthorized_resource = resource.ErrorPage(http.UNAUTHORIZED, |
1593 | - "Unauthorized", "Unauthorized") |
1594 | - root.putChild(UNAUTHORIZED, unauthorized_resource) |
1595 | - root.putChild(HEADONLY, HeadOnlyResource()) |
1596 | - root.putChild(VERIFYHEADERS, VerifyHeadersResource()) |
1597 | - root.putChild(VERIFYPOSTPARAMS, VerifyPostParameters()) |
1598 | - root.putChild(AUTHRESOURCE, AuthCheckerResource()) |
1599 | - |
1600 | - db = checkers.InMemoryUsernamePasswordDatabaseDontUse() |
1601 | - db.addUser(SAMPLE_USERNAME, SAMPLE_PASSWORD) |
1602 | - test_portal = portal.Portal(SimpleRealm(), [db]) |
1603 | - cred_factory = guard.BasicCredentialFactory("example.org") |
1604 | - guarded_resource = guard.HTTPAuthSessionWrapper( |
1605 | - test_portal, [cred_factory]) |
1606 | - root.putChild(GUARDED, guarded_resource) |
1607 | - return root |
1608 | - |
1609 | - |
1610 | -class HTTPMockWebServer(HTTPWebServer): |
1611 | - """A mock webserver for the webclient tests.""" |
1612 | - |
1613 | - def __init__(self): |
1614 | - """Create a new instance.""" |
1615 | - root = get_root_resource() |
1616 | - super(HTTPMockWebServer, self).__init__(root) |
1617 | - |
1618 | - |
1619 | -class HTTPSMockWebServer(HTTPSWebServer): |
1620 | - """A mock webserver for the webclient tests.""" |
1621 | - |
1622 | - def __init__(self, ssl_settings): |
1623 | - """Create a new instance.""" |
1624 | - root = get_root_resource() |
1625 | - super(HTTPSMockWebServer, self).__init__(root, ssl_settings) |
1626 | - |
1627 | - |
1628 | -class ModuleSelectionTestCase(TestCase): |
1629 | - """Test the functions to choose the txweb or libsoup backend.""" |
1630 | - |
1631 | - def assert_module_name(self, module, expected_name): |
1632 | - """Check the name of a given module.""" |
1633 | - module_filename = os.path.basename(module.__file__) |
1634 | - module_name = os.path.splitext(module_filename)[0] |
1635 | - self.assertEqual(module_name, expected_name) |
1636 | - |
1637 | - def test_webclient_module_libsoup(self): |
1638 | - """Test the module name for the libsoup case.""" |
1639 | - module = webclient.webclient_module() |
1640 | - self.assert_module_name(module, "libsoup") |
1641 | - |
1642 | - |
1643 | -class WebClientTestCase(TestCase): |
1644 | - """Test for the webclient.""" |
1645 | - |
1646 | - timeout = 1 |
1647 | - webclient_factory = webclient.webclient_factory |
1648 | - |
1649 | - @defer.inlineCallbacks |
1650 | - def setUp(self): |
1651 | - yield super(WebClientTestCase, self).setUp() |
1652 | - self.wc = self.webclient_factory() |
1653 | - self.addCleanup(self.wc.shutdown) |
1654 | - self.ws = HTTPMockWebServer() |
1655 | - self.ws.start() |
1656 | - self.addCleanup(self.ws.stop) |
1657 | - self.base_iri = self.ws.get_iri() |
1658 | - |
1659 | - @defer.inlineCallbacks |
1660 | - def test_request_takes_an_iri(self): |
1661 | - """Passing a non-unicode iri fails.""" |
1662 | - d = self.wc.request(bytes(self.base_iri + SIMPLERESOURCE)) |
1663 | - yield self.assertFailure(d, TypeError) |
1664 | - |
1665 | - @defer.inlineCallbacks |
1666 | - def test_get_iri(self): |
1667 | - """Passing in a unicode iri works fine.""" |
1668 | - result = yield self.wc.request(self.base_iri + SIMPLERESOURCE) |
1669 | - self.assertEqual(SAMPLE_RESOURCE, result.content) |
1670 | - |
1671 | - @defer.inlineCallbacks |
1672 | - def test_get_iri_error(self): |
1673 | - """The errback is called when there's some error.""" |
1674 | - yield self.assertFailure(self.wc.request(self.base_iri + THROWERROR), |
1675 | - WebClientError) |
1676 | - |
1677 | - @defer.inlineCallbacks |
1678 | - def test_zero_byte_in_content(self): |
1679 | - """Test a reply with a nul byte in the middle of it.""" |
1680 | - result = yield self.wc.request(self.base_iri + BYTEZERORESOURCE) |
1681 | - self.assertEqual(SAMPLE_JPEG_HEADER, result.content) |
1682 | - |
1683 | - @defer.inlineCallbacks |
1684 | - def test_post(self): |
1685 | - """Test a post request.""" |
1686 | - result = yield self.wc.request(self.base_iri + POSTABLERESOURCE, |
1687 | - method="POST") |
1688 | - self.assertEqual(SAMPLE_RESOURCE, result.content) |
1689 | - |
1690 | - @defer.inlineCallbacks |
1691 | - def test_post_with_args(self): |
1692 | - """Test a post request with arguments.""" |
1693 | - args = urlencode(SAMPLE_POST_PARAMS) |
1694 | - iri = self.base_iri + VERIFYPOSTPARAMS + "?" + args |
1695 | - headers = { |
1696 | - "content-type": "application/x-www-form-urlencoded", |
1697 | - } |
1698 | - result = yield self.wc.request( |
1699 | - iri, method="POST", extra_headers=headers, post_content=args) |
1700 | - self.assertEqual(SAMPLE_RESOURCE, result.content) |
1701 | - |
1702 | - @defer.inlineCallbacks |
1703 | - def test_unauthorized(self): |
1704 | - """Detect when a request failed with the UNAUTHORIZED http code.""" |
1705 | - yield self.assertFailure(self.wc.request(self.base_iri + UNAUTHORIZED), |
1706 | - UnauthorizedError) |
1707 | - |
1708 | - @defer.inlineCallbacks |
1709 | - def test_method_head(self): |
1710 | - """The HTTP method is used.""" |
1711 | - result = yield self.wc.request(self.base_iri + HEADONLY, method="HEAD") |
1712 | - self.assertEqual("", result.content) |
1713 | - |
1714 | - @defer.inlineCallbacks |
1715 | - def test_send_extra_headers(self): |
1716 | - """The extra_headers are sent to the server.""" |
1717 | - result = yield self.wc.request(self.base_iri + VERIFYHEADERS, |
1718 | - extra_headers=SAMPLE_HEADERS) |
1719 | - self.assertIn(SAMPLE_KEY, result.headers) |
1720 | - self.assertEqual(result.headers[SAMPLE_KEY], [SAMPLE_VALUE]) |
1721 | - |
1722 | - @defer.inlineCallbacks |
1723 | - def test_send_basic_auth(self): |
1724 | - """The basic authentication headers are sent.""" |
1725 | - other_wc = self.webclient_factory(username=SAMPLE_USERNAME, |
1726 | - password=SAMPLE_PASSWORD) |
1727 | - self.addCleanup(other_wc.shutdown) |
1728 | - result = yield other_wc.request(self.base_iri + GUARDED) |
1729 | - self.assertEqual(SAMPLE_RESOURCE, result.content) |
1730 | - |
1731 | - @defer.inlineCallbacks |
1732 | - def test_send_basic_auth_wrong_credentials(self): |
1733 | - """Wrong credentials returns a webclient error.""" |
1734 | - other_wc = self.webclient_factory(username=SAMPLE_USERNAME, |
1735 | - password="wrong password!") |
1736 | - self.addCleanup(other_wc.shutdown) |
1737 | - yield self.assertFailure(other_wc.request(self.base_iri + GUARDED), |
1738 | - UnauthorizedError) |
1739 | - |
1740 | - @defer.inlineCallbacks |
1741 | - def test_request_is_auth_signed(self): |
1742 | - """The request is auth signed.""" |
1743 | - tsc = self.wc.get_timestamp_checker() |
1744 | - self.patch(tsc, "get_faithful_time", lambda: defer.succeed('1')) |
1745 | - result = yield self.wc.request(self.base_iri + AUTHRESOURCE, |
1746 | - auth_credentials=SAMPLE_CREDENTIALS) |
1747 | - self.assertEqual(SAMPLE_RESOURCE, result.content) |
1748 | - |
1749 | - @defer.inlineCallbacks |
1750 | - def test_auth_signing_uses_timestamp(self): |
1751 | - """Auth signing uses the timestamp.""" |
1752 | - called = [] |
1753 | - |
1754 | - def fake_get_faithful_time(): |
1755 | - """A fake get_timestamp""" |
1756 | - called.append(True) |
1757 | - return defer.succeed('1') |
1758 | - |
1759 | - tsc = self.wc.get_timestamp_checker() |
1760 | - self.patch(tsc, "get_faithful_time", fake_get_faithful_time) |
1761 | - yield self.wc.request(self.base_iri + AUTHRESOURCE, |
1762 | - auth_credentials=SAMPLE_CREDENTIALS) |
1763 | - self.assertTrue(called, "The timestamp must be retrieved.") |
1764 | - |
1765 | - @defer.inlineCallbacks |
1766 | - def test_returned_content_are_bytes(self): |
1767 | - """The returned content are bytes.""" |
1768 | - tsc = self.wc.get_timestamp_checker() |
1769 | - self.patch(tsc, "get_faithful_time", lambda: defer.succeed('1')) |
1770 | - result = yield self.wc.request(self.base_iri + AUTHRESOURCE, |
1771 | - auth_credentials=SAMPLE_CREDENTIALS) |
1772 | - self.assertTrue(isinstance(result.content, bytes), |
1773 | - "The type of %r must be bytes" % result.content) |
1774 | - |
1775 | - @defer.inlineCallbacks |
1776 | - def test_webclienterror_not_string(self): |
1777 | - """The returned exception contains unicode data.""" |
1778 | - deferred = self.wc.request(self.base_iri + THROWERROR) |
1779 | - failure = yield self.assertFailure(deferred, WebClientError) |
1780 | - for error in failure.args: |
1781 | - self.assertTrue(isinstance(error, basestring)) |
1782 | - |
1783 | - |
1784 | -class FakeSavingReactor(object): |
1785 | - """A fake reactor that saves connection attempts.""" |
1786 | - |
1787 | - def __init__(self): |
1788 | - """Initialize this fake instance.""" |
1789 | - self.connections = [] |
1790 | - |
1791 | - def connectTCP(self, host, port, factory, *args): |
1792 | - """Fake the connection.""" |
1793 | - self.connections.append((host, port, args)) |
1794 | - factory.response_headers = {} |
1795 | - factory.deferred = defer.succeed("response content") |
1796 | - |
1797 | - def connectSSL(self, host, port, factory, *args): |
1798 | - """Fake the connection.""" |
1799 | - self.connections.append((host, port, args)) |
1800 | - factory.response_headers = {} |
1801 | - factory.deferred = defer.succeed("response content") |
1802 | - |
1803 | - |
1804 | -class TxWebClientTestCase(WebClientTestCase): |
1805 | - """Test case for txweb.""" |
1806 | - |
1807 | - webclient_factory = txweb.WebClient |
1808 | - |
1809 | - @defer.inlineCallbacks |
1810 | - def setUp(self): |
1811 | - """Set the diff tests.""" |
1812 | - # delay import, otherwise a default reactor gets installed |
1813 | - from twisted.web import client |
1814 | - self.factory = client.HTTPClientFactory |
1815 | - # set the factory to be used |
1816 | - # Hook the server's buildProtocol to make the protocol instance |
1817 | - # accessible to tests and ensure that the reactor is clean! |
1818 | - build_protocol = self.factory.buildProtocol |
1819 | - self.serverProtocol = None |
1820 | - |
1821 | - def remember_protocol_instance(my_self, addr): |
1822 | - """Remember the protocol used in the test.""" |
1823 | - protocol = build_protocol(my_self, addr) |
1824 | - self.serverProtocol = protocol |
1825 | - on_connection_lost = defer.Deferred() |
1826 | - connection_lost = protocol.connectionLost |
1827 | - |
1828 | - def defer_connection_lost(protocol, *a): |
1829 | - """Lost connection.""" |
1830 | - if not on_connection_lost.called: |
1831 | - on_connection_lost.callback(None) |
1832 | - connection_lost(protocol, *a) |
1833 | - |
1834 | - self.patch(protocol, 'connectionLost', defer_connection_lost) |
1835 | - |
1836 | - def cleanup(): |
1837 | - """Clean the connection.""" |
1838 | - if self.serverProtocol.transport is not None: |
1839 | - self.serverProtocol.transport.loseConnection() |
1840 | - return on_connection_lost |
1841 | - |
1842 | - self.addCleanup(cleanup) |
1843 | - return protocol |
1844 | - |
1845 | - self.factory.buildProtocol = remember_protocol_instance |
1846 | - self.addCleanup(self.set_build_protocol, build_protocol) |
1847 | - txweb.WebClient.client_factory = self.factory |
1848 | - |
1849 | - yield super(TxWebClientTestCase, self).setUp() |
1850 | - |
1851 | - def set_build_protocol(self, method): |
1852 | - """Set the method back.""" |
1853 | - self.factory.buildProtocol = method |
1854 | - |
1855 | - |
1856 | -class TxWebClientReactorReplaceableTestCase(TestCase): |
1857 | - """In the txweb client the reactor is replaceable.""" |
1858 | - |
1859 | - timeout = 3 |
1860 | - FAKE_HOST = u"fake" |
1861 | - FAKE_IRI_TEMPLATE = u"%%s://%s/fake_page" % FAKE_HOST |
1862 | - |
1863 | - @defer.inlineCallbacks |
1864 | - def _test_replaceable_reactor(self, iri): |
1865 | - """The reactor can be replaced with the tunnel client.""" |
1866 | - fake_reactor = FakeSavingReactor() |
1867 | - wc = txweb.WebClient(fake_reactor) |
1868 | - _response = yield wc.request(iri) |
1869 | - assert(_response) |
1870 | - host, _port, _args = fake_reactor.connections[0] |
1871 | - self.assertEqual(host, self.FAKE_HOST) |
1872 | - |
1873 | - def test_replaceable_reactor_http(self): |
1874 | - """Test the replaceable reactor with an http iri.""" |
1875 | - return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "http") |
1876 | - |
1877 | - def test_replaceable_reactor_https(self): |
1878 | - """Test the replaceable reactor with an https iri.""" |
1879 | - return self._test_replaceable_reactor(self.FAKE_IRI_TEMPLATE % "https") |
1880 | - |
1881 | - |
1882 | -class TimestampCheckerTestCase(TestCase): |
1883 | - """Tests for the timestampchecker classmethod.""" |
1884 | - |
1885 | - @defer.inlineCallbacks |
1886 | - def setUp(self): |
1887 | - """Initialize this testcase.""" |
1888 | - yield super(TimestampCheckerTestCase, self).setUp() |
1889 | - self.wc = webclient.webclient_factory() |
1890 | - self.patch(self.wc.__class__, "timestamp_checker", None) |
1891 | - |
1892 | - def test_timestamp_checker_has_the_same_class_as_the_creator(self): |
1893 | - """The TimestampChecker has the same class.""" |
1894 | - tsc = self.wc.get_timestamp_checker() |
1895 | - self.assertEqual(tsc.webclient_class, self.wc.__class__) |
1896 | - |
1897 | - def test_timestamp_checker_is_the_same_for_all_webclients(self): |
1898 | - """The TimestampChecker is the same for all webclients.""" |
1899 | - tsc1 = self.wc.get_timestamp_checker() |
1900 | - wc2 = webclient.webclient_factory() |
1901 | - tsc2 = wc2.get_timestamp_checker() |
1902 | - self.assertIs(tsc1, tsc2) |
1903 | - |
1904 | - |
1905 | -class BasicProxyTestCase(SquidTestCase): |
1906 | - """Test that the proxy works at all.""" |
1907 | - |
1908 | - timeout = 3 |
1909 | - |
1910 | - @defer.inlineCallbacks |
1911 | - def setUp(self): |
1912 | - yield super(BasicProxyTestCase, self).setUp() |
1913 | - self.ws = HTTPMockWebServer() |
1914 | - self.ws.start() |
1915 | - self.addCleanup(self.ws.stop) |
1916 | - self.base_iri = self.ws.get_iri() |
1917 | - self.wc = webclient.webclient_factory() |
1918 | - self.addCleanup(self.wc.shutdown) |
1919 | - |
1920 | - def assert_header_contains(self, headers, expected): |
1921 | - """One of the headers matching key must contain a given value.""" |
1922 | - self.assertTrue(any(expected in value for value in headers)) |
1923 | - |
1924 | - @defer.inlineCallbacks |
1925 | - def test_anonymous_proxy_is_used(self): |
1926 | - """The anonymous proxy is used by the webclient.""" |
1927 | - settings = self.get_nonauth_proxy_settings() |
1928 | - self.wc.force_use_proxy(settings) |
1929 | - result = yield self.wc.request(self.base_iri + SIMPLERESOURCE) |
1930 | - self.assert_header_contains(result.headers["Via"], "squid") |
1931 | - |
1932 | - @skipIfOS('linux2', |
1933 | - 'LP: #1111880 - ncsa_auth crashing for auth proxy tests.') |
1934 | - @defer.inlineCallbacks |
1935 | - def test_authenticated_proxy_is_used(self): |
1936 | - """The authenticated proxy is used by the webclient.""" |
1937 | - settings = self.get_auth_proxy_settings() |
1938 | - self.wc.force_use_proxy(settings) |
1939 | - result = yield self.wc.request(self.base_iri + SIMPLERESOURCE) |
1940 | - self.assert_header_contains(result.headers["Via"], "squid") |
1941 | - |
1942 | - if WEBCLIENT_MODULE_NAME.endswith(".txweb"): |
1943 | - reason = "txweb does not support proxies." |
1944 | - test_anonymous_proxy_is_used.skip = reason |
1945 | - test_authenticated_proxy_is_used.kip = reason |
1946 | - |
1947 | - |
1948 | -class HeaderDictTestCase(TestCase): |
1949 | - """Tests for the case insensitive header dictionary.""" |
1950 | - |
1951 | - def test_constructor_handles_keys(self): |
1952 | - """The constructor handles case-insensitive keys.""" |
1953 | - hd = HeaderDict({"ClAvE": "value"}) |
1954 | - self.assertIn("clave", hd) |
1955 | - |
1956 | - def test_can_set_get_items(self): |
1957 | - """The item is set/getted.""" |
1958 | - hd = HeaderDict() |
1959 | - hd["key"] = "value" |
1960 | - hd["KEY"] = "value2" |
1961 | - self.assertEqual(hd["key"], "value2") |
1962 | - |
1963 | - def test_can_test_presence(self): |
1964 | - """The presence of an item is found.""" |
1965 | - hd = HeaderDict() |
1966 | - self.assertNotIn("cLaVe", hd) |
1967 | - hd["CLAVE"] = "value1" |
1968 | - self.assertIn("cLaVe", hd) |
1969 | - del(hd["cLAVe"]) |
1970 | - self.assertNotIn("cLaVe", hd) |
1971 | - |
1972 | - |
1973 | -class FakeKeyring(object): |
1974 | - """A fake keyring.""" |
1975 | - |
1976 | - def __init__(self, creds): |
1977 | - """A fake keyring.""" |
1978 | - self.creds = creds |
1979 | - |
1980 | - def __call__(self): |
1981 | - """Fake instance callable.""" |
1982 | - return self |
1983 | - |
1984 | - def get_credentials(self, domain): |
1985 | - """A fake get_credentials.""" |
1986 | - if isinstance(self.creds, Exception): |
1987 | - return defer.fail(self.creds) |
1988 | - return defer.succeed(self.creds) |
1989 | - |
1990 | - |
1991 | -class BaseSSLTestCase(SquidTestCase): |
1992 | - """Base test that allows to use ssl connections.""" |
1993 | - |
1994 | - @defer.inlineCallbacks |
1995 | - def setUp(self): |
1996 | - """Set the diff tests.""" |
1997 | - yield super(BaseSSLTestCase, self).setUp() |
1998 | - self.cert_dir = os.path.join(self.tmpdir, 'cert') |
1999 | - self.cert_details = dict(organization='Canonical', |
2000 | - common_name=gethostname(), |
2001 | - locality_name='London', |
2002 | - unit='Ubuntu One', |
2003 | - country_name='UK', |
2004 | - state_name='London',) |
2005 | - self.ssl_settings = self._generate_self_signed_certificate( |
2006 | - self.cert_dir, |
2007 | - self.cert_details) |
2008 | - self.addCleanup(self._clean_ssl_certificate_files) |
2009 | - |
2010 | - self.ws = HTTPSMockWebServer(self.ssl_settings) |
2011 | - self.ws.start() |
2012 | - self.addCleanup(self.ws.stop) |
2013 | - self.base_iri = self.ws.get_iri() |
2014 | - |
2015 | - def _clean_ssl_certificate_files(self): |
2016 | - """Remove the certificate files.""" |
2017 | - if os.path.exists(self.cert_dir): |
2018 | - shutil.rmtree(self.cert_dir) |
2019 | - |
2020 | - def _generate_self_signed_certificate(self, cert_dir, cert_details): |
2021 | - """Generate the required SSL certificates.""" |
2022 | - if not os.path.exists(cert_dir): |
2023 | - os.makedirs(cert_dir) |
2024 | - cert_path = os.path.join(cert_dir, 'cert.crt') |
2025 | - key_path = os.path.join(cert_dir, 'cert.key') |
2026 | - |
2027 | - if os.path.exists(cert_path): |
2028 | - os.unlink(cert_path) |
2029 | - if os.path.exists(key_path): |
2030 | - os.unlink(key_path) |
2031 | - |
2032 | - # create a key pair |
2033 | - key = crypto.PKey() |
2034 | - key.generate_key(crypto.TYPE_RSA, 1024) |
2035 | - |
2036 | - # create a self-signed cert |
2037 | - cert = crypto.X509() |
2038 | - cert.get_subject().C = cert_details['country_name'] |
2039 | - cert.get_subject().ST = cert_details['state_name'] |
2040 | - cert.get_subject().L = cert_details['locality_name'] |
2041 | - cert.get_subject().O = cert_details['organization'] |
2042 | - cert.get_subject().OU = cert_details['unit'] |
2043 | - cert.get_subject().CN = cert_details['common_name'] |
2044 | - cert.set_serial_number(1000) |
2045 | - cert.gmtime_adj_notBefore(0) |
2046 | - cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) |
2047 | - cert.set_issuer(cert.get_subject()) |
2048 | - cert.set_pubkey(key) |
2049 | - cert.sign(key, 'sha1') |
2050 | - |
2051 | - with open(cert_path, 'wt') as fd: |
2052 | - fd.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) |
2053 | - |
2054 | - with open(key_path, 'wt') as fd: |
2055 | - fd.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) |
2056 | - |
2057 | - return dict(key=key_path, cert=cert_path) |
2058 | - |
2059 | - |
2060 | -class CorrectProxyTestCase(BaseSSLTestCase): |
2061 | - """Test the interaction with a SSL enabled proxy.""" |
2062 | - |
2063 | - @defer.inlineCallbacks |
2064 | - def setUp(self): |
2065 | - """Set the tests.""" |
2066 | - yield super(CorrectProxyTestCase, self).setUp() |
2067 | - |
2068 | - # fake the gsettings to have diff settings for https and http |
2069 | - http_settings = self.get_auth_proxy_settings() |
2070 | - https_settings = self.get_nonauth_proxy_settings() |
2071 | - |
2072 | - proxy_settings = dict(http=http_settings, https=https_settings) |
2073 | - self.patch(gsettings, "get_proxy_settings", lambda: proxy_settings) |
2074 | - |
2075 | - self.wc = webclient.webclient_factory() |
2076 | - self.addCleanup(self.wc.shutdown) |
2077 | - |
2078 | - self.called = [] |
2079 | - |
2080 | - def assert_header_contains(self, headers, expected): |
2081 | - """One of the headers matching key must contain a given value.""" |
2082 | - self.assertTrue(any(expected in value for value in headers)) |
2083 | - |
2084 | - @defer.inlineCallbacks |
2085 | - def test_https_request(self): |
2086 | - """Test using the correct proxy for the ssl request. |
2087 | - |
2088 | - In order to assert that the correct proxy is used we expect not to call |
2089 | - the auth dialog since we set the https proxy not to use the auth proxy |
2090 | - and to fail because we are reaching a https page with bad self-signed |
2091 | - certs. |
2092 | - """ |
2093 | - # we fail due to the fake ssl cert |
2094 | - yield self.failUnlessFailure(self.wc.request( |
2095 | - self.base_iri + SIMPLERESOURCE), |
2096 | - WebClientError) |
2097 | - # https requests do not use the auth proxy therefore called should be |
2098 | - # empty. This asserts that we are using the correct settings for the |
2099 | - # request. |
2100 | - self.assertEqual([], self.called) |
2101 | - |
2102 | - @defer.inlineCallbacks |
2103 | - def test_http_request(self): |
2104 | - """Test using the correct proxy for the plain request. |
2105 | - |
2106 | - This tests does the opposite to the https tests. We did set the auth |
2107 | - proxy for the http request therefore we expect the proxy dialog to be |
2108 | - used and not to get an error since we are not visiting a https with bad |
2109 | - self-signed certs. |
2110 | - """ |
2111 | - # we do not fail since we are not going to the https page |
2112 | - result = yield self.wc.request(self.base_iri + SIMPLERESOURCE) |
2113 | - self.assert_header_contains(result.headers["Via"], "squid") |
2114 | - |
2115 | - if WEBCLIENT_MODULE_NAME.endswith(".txweb"): |
2116 | - reason = 'Multiple proxy settings is not supported.' |
2117 | - test_https_request.skip = reason |
2118 | - test_http_request.skip = reason |
2119 | - |
2120 | - if WEBCLIENT_MODULE_NAME.endswith(".libsoup"): |
2121 | - reason = 'Hard to test since we need to fully mock gsettings.' |
2122 | - test_https_request.skip = reason |
2123 | - test_http_request.skip = reason |
2124 | - |
2125 | - |
2126 | -class SSLTestCase(BaseSSLTestCase): |
2127 | - """Test error handling when dealing with ssl.""" |
2128 | - |
2129 | - @defer.inlineCallbacks |
2130 | - def setUp(self): |
2131 | - """Set the diff tests.""" |
2132 | - yield super(SSLTestCase, self).setUp() |
2133 | - |
2134 | - self.memento = MementoHandler() |
2135 | - self.memento.setLevel(logging.DEBUG) |
2136 | - logger = webclient.webclient_module().logger |
2137 | - logger.addHandler(self.memento) |
2138 | - self.addCleanup(logger.removeHandler, self.memento) |
2139 | - |
2140 | - self.wc = webclient.webclient_factory() |
2141 | - self.addCleanup(self.wc.shutdown) |
2142 | - |
2143 | - self.called = [] |
2144 | - |
2145 | - def test_ssl_fail(self): |
2146 | - """Test showing the dialog and rejecting.""" |
2147 | - self.failUnlessFailure(self.wc.request( |
2148 | - self.base_iri + SIMPLERESOURCE), WebClientError) |
2149 | - self.assertNotEqual(None, self.memento.check_error('SSL errors')) |
2150 | - |
2151 | - if (WEBCLIENT_MODULE_NAME.endswith(".txweb") or |
2152 | - WEBCLIENT_MODULE_NAME.endswith(".libsoup")): |
2153 | - reason = 'SSL support has not yet been implemented.' |
2154 | - test_ssl_fail.skip = reason |
2155 | |
2156 | === removed file 'ubuntuone/utils/webclient/timestamp.py' |
2157 | --- ubuntuone/utils/webclient/timestamp.py 2016-06-04 21:14:35 +0000 |
2158 | +++ ubuntuone/utils/webclient/timestamp.py 1970-01-01 00:00:00 +0000 |
2159 | @@ -1,93 +0,0 @@ |
2160 | -# -*- coding: utf-8 -*- |
2161 | -# |
2162 | -# Copyright 2011-2013 Canonical Ltd. |
2163 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
2164 | -# |
2165 | -# This program is free software: you can redistribute it and/or modify it |
2166 | -# under the terms of the GNU General Public License version 3, as published |
2167 | -# by the Free Software Foundation. |
2168 | -# |
2169 | -# This program is distributed in the hope that it will be useful, but |
2170 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
2171 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2172 | -# PURPOSE. See the GNU General Public License for more details. |
2173 | -# |
2174 | -# You should have received a copy of the GNU General Public License along |
2175 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
2176 | -# |
2177 | -# In addition, as a special exception, the copyright holders give |
2178 | -# permission to link the code of portions of this program with the |
2179 | -# OpenSSL library under certain conditions as described in each |
2180 | -# individual source file, and distribute linked combinations |
2181 | -# including the two. |
2182 | -# You must obey the GNU General Public License in all respects |
2183 | -# for all of the code used other than OpenSSL. If you modify |
2184 | -# file(s) with this exception, you may extend this exception to your |
2185 | -# version of the file(s), but you are not obligated to do so. If you |
2186 | -# do not wish to do so, delete this exception statement from your |
2187 | -# version. If you delete this exception statement from all source |
2188 | -# files in the program, then also delete it here. |
2189 | - |
2190 | -"""Timestamp synchronization with the server.""" |
2191 | - |
2192 | -import logging |
2193 | -import time |
2194 | - |
2195 | -from twisted.internet import defer |
2196 | - |
2197 | - |
2198 | -logger = logging.getLogger(__name__) |
2199 | -NOCACHE_HEADERS = {"Cache-Control": "no-cache"} |
2200 | - |
2201 | - |
2202 | -class TimestampChecker(object): |
2203 | - """A timestamp that's regularly checked with a server.""" |
2204 | - |
2205 | - CHECKING_INTERVAL = 60 * 60 # in seconds |
2206 | - ERROR_INTERVAL = 30 # in seconds |
2207 | - SERVER_IRI = 'http://google.com' |
2208 | - |
2209 | - def __init__(self, webclient_class): |
2210 | - """Initialize this instance.""" |
2211 | - self.next_check = time.time() |
2212 | - self.skew = 0 |
2213 | - self.webclient_class = webclient_class |
2214 | - |
2215 | - @defer.inlineCallbacks |
2216 | - def get_server_date_header(self, server_iri): |
2217 | - """Get the server date using twisted webclient.""" |
2218 | - webclient = self.webclient_class() |
2219 | - try: |
2220 | - response = yield webclient.request(server_iri, method="HEAD", |
2221 | - extra_headers=NOCACHE_HEADERS) |
2222 | - defer.returnValue(response.headers["Date"][0]) |
2223 | - finally: |
2224 | - webclient.shutdown() |
2225 | - |
2226 | - @defer.inlineCallbacks |
2227 | - def get_server_time(self): |
2228 | - """Get the time at the server.""" |
2229 | - date_string = yield self.get_server_date_header(self.SERVER_IRI) |
2230 | - # delay import, otherwise a default reactor gets installed |
2231 | - from twisted.web import http |
2232 | - timestamp = http.stringToDatetime(date_string) |
2233 | - defer.returnValue(timestamp) |
2234 | - |
2235 | - @defer.inlineCallbacks |
2236 | - def get_faithful_time(self): |
2237 | - """Get an accurate timestamp.""" |
2238 | - local_time = time.time() |
2239 | - if local_time >= self.next_check: |
2240 | - try: |
2241 | - server_time = yield self.get_server_time() |
2242 | - self.next_check = local_time + self.CHECKING_INTERVAL |
2243 | - self.skew = server_time - local_time |
2244 | - logger.debug("Calculated server time skew: %r", self.skew) |
2245 | - except Exception as e: |
2246 | - logger.debug("Error while verifying server time skew: %r", e) |
2247 | - self.next_check = local_time + self.ERROR_INTERVAL |
2248 | - # delay import, otherwise a default reactor gets installed |
2249 | - from twisted.web import http |
2250 | - logger.debug("Using corrected timestamp: %r", |
2251 | - http.datetimeToString(local_time + self.skew)) |
2252 | - defer.returnValue(int(local_time + self.skew)) |
2253 | |
2254 | === removed file 'ubuntuone/utils/webclient/txweb.py' |
2255 | --- ubuntuone/utils/webclient/txweb.py 2016-06-04 21:14:35 +0000 |
2256 | +++ ubuntuone/utils/webclient/txweb.py 1970-01-01 00:00:00 +0000 |
2257 | @@ -1,175 +0,0 @@ |
2258 | -# -*- coding: utf-8 -*- |
2259 | -# |
2260 | -# Copyright 2011-2012 Canonical Ltd. |
2261 | -# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros) |
2262 | -# |
2263 | -# This program is free software: you can redistribute it and/or modify it |
2264 | -# under the terms of the GNU General Public License version 3, as published |
2265 | -# by the Free Software Foundation. |
2266 | -# |
2267 | -# This program is distributed in the hope that it will be useful, but |
2268 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
2269 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2270 | -# PURPOSE. See the GNU General Public License for more details. |
2271 | -# |
2272 | -# You should have received a copy of the GNU General Public License along |
2273 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
2274 | -# |
2275 | -# In addition, as a special exception, the copyright holders give |
2276 | -# permission to link the code of portions of this program with the |
2277 | -# OpenSSL library under certain conditions as described in each |
2278 | -# individual source file, and distribute linked combinations |
2279 | -# including the two. |
2280 | -# You must obey the GNU General Public License in all respects |
2281 | -# for all of the code used other than OpenSSL. If you modify |
2282 | -# file(s) with this exception, you may extend this exception to your |
2283 | -# version of the file(s), but you are not obligated to do so. If you |
2284 | -# do not wish to do so, delete this exception statement from your |
2285 | -# version. If you delete this exception statement from all source |
2286 | -# files in the program, then also delete it here. |
2287 | - |
2288 | -"""A webclient backend that uses twisted.web.client.""" |
2289 | - |
2290 | -import base64 |
2291 | -import logging |
2292 | - |
2293 | -try: |
2294 | - from urllib.parse import urlparse |
2295 | -except ImportError: |
2296 | - from urlparse import urlparse |
2297 | - |
2298 | -from twisted.internet import defer |
2299 | - |
2300 | -from ubuntuone.utils.webclient.common import ( |
2301 | - BaseWebClient, |
2302 | - HeaderDict, |
2303 | - Response, |
2304 | - UnauthorizedError, |
2305 | - WebClientError, |
2306 | -) |
2307 | - |
2308 | -logger = logging.getLogger(__name__) |
2309 | - |
2310 | - |
2311 | -class RawResponse(object): |
2312 | - """A raw response from the webcall.""" |
2313 | - |
2314 | - def __init__(self, headers, content, code=200, phrase="OK"): |
2315 | - """Initialize this response.""" |
2316 | - self.headers = headers |
2317 | - self.content = content |
2318 | - self.code = code |
2319 | - self.phrase = phrase |
2320 | - |
2321 | - |
2322 | -class WebClient(BaseWebClient): |
2323 | - """A simple web client that does not support proxies, yet.""" |
2324 | - |
2325 | - client_factory = None |
2326 | - |
2327 | - def __init__(self, connector=None, context_factory=None, **kwargs): |
2328 | - """Initialize this webclient.""" |
2329 | - # delay import, otherwise a default reactor gets installed |
2330 | - from twisted.web import client |
2331 | - |
2332 | - super(WebClient, self).__init__(**kwargs) |
2333 | - |
2334 | - if connector is None: |
2335 | - from twisted.internet import reactor |
2336 | - self.connector = reactor |
2337 | - else: |
2338 | - self.connector = connector |
2339 | - |
2340 | - if context_factory is None: |
2341 | - from twisted.internet import ssl |
2342 | - self.context_factory = ssl.ClientContextFactory() |
2343 | - else: |
2344 | - self.context_factory = context_factory |
2345 | - |
2346 | - # decide which client factory to use |
2347 | - if WebClient.client_factory is None: |
2348 | - self.client_factory = client.HTTPClientFactory |
2349 | - else: |
2350 | - self.client_factory = WebClient.client_factory |
2351 | - |
2352 | - @defer.inlineCallbacks |
2353 | - def raw_request(self, method, uri, headers, postdata): |
2354 | - """Make a raw http request.""" |
2355 | - # Twisted wants headers as bytes, but because of other libs, they might |
2356 | - # be unicodes. Assume utf-8 and revert the encodings. |
2357 | - bytes_headers = {} |
2358 | - for key, value in headers.items(): |
2359 | - if isinstance(key, unicode): |
2360 | - key = key.encode('utf-8') |
2361 | - if isinstance(value, unicode): |
2362 | - value = value.encode('utf-8') |
2363 | - bytes_headers[key] = value |
2364 | - headers = bytes_headers |
2365 | - |
2366 | - # delay import, otherwise a default reactor gets installed |
2367 | - from twisted.web import error |
2368 | - |
2369 | - parsed_url = urlparse(uri) |
2370 | - |
2371 | - https = parsed_url.scheme == "https" |
2372 | - host = parsed_url.netloc.split(":")[0] |
2373 | - if parsed_url.port is None: |
2374 | - port = 443 if https else 80 |
2375 | - else: |
2376 | - port = parsed_url.port |
2377 | - |
2378 | - factory = self.client_factory( |
2379 | - uri, method=method, postdata=postdata, headers=headers, |
2380 | - followRedirect=False) |
2381 | - if https: |
2382 | - self.connector.connectSSL(host, port, factory, |
2383 | - self.context_factory) |
2384 | - else: |
2385 | - self.connector.connectTCP(host, port, factory) |
2386 | - |
2387 | - try: |
2388 | - content = yield factory.deferred |
2389 | - response = RawResponse(factory.response_headers, content) |
2390 | - except error.Error as e: |
2391 | - response = RawResponse(factory.response_headers, e.response, |
2392 | - int(e.status), e.message) |
2393 | - defer.returnValue(response) |
2394 | - |
2395 | - @defer.inlineCallbacks |
2396 | - def request(self, iri, method="GET", extra_headers=None, |
2397 | - auth_credentials=None, post_content=None): |
2398 | - """Get the page, or fail trying.""" |
2399 | - # delay import, otherwise a default reactor gets installed |
2400 | - from twisted.web import http |
2401 | - |
2402 | - uri = self.iri_to_uri(iri) |
2403 | - headers = yield self.build_request_headers(uri, method, extra_headers, |
2404 | - auth_credentials) |
2405 | - |
2406 | - if self.username and self.password: |
2407 | - auth = base64.b64encode(self.username + ":" + self.password) |
2408 | - headers["Authorization"] = "Basic " + auth |
2409 | - |
2410 | - try: |
2411 | - raw_response = yield self.raw_request(method, uri, |
2412 | - headers=headers, |
2413 | - postdata=post_content) |
2414 | - response_headers = HeaderDict(raw_response.headers) |
2415 | - if method.lower() != "head": |
2416 | - response_content = raw_response.content |
2417 | - else: |
2418 | - response_content = "" |
2419 | - if raw_response.code == http.OK: |
2420 | - defer.returnValue(Response(response_content, response_headers)) |
2421 | - if raw_response.code == http.UNAUTHORIZED: |
2422 | - raise UnauthorizedError(raw_response.phrase, |
2423 | - response_content) |
2424 | - raise WebClientError(raw_response.phrase, response_content) |
2425 | - except WebClientError: |
2426 | - raise |
2427 | - except Exception as e: |
2428 | - raise WebClientError(e.message, e) |
2429 | - |
2430 | - def force_use_proxy(self, settings): |
2431 | - """Setup this webclient to use the given proxy settings.""" |
2432 | - # No direct proxy support in twisted.web.client |
Looks good! Thanks