Merge lp:~facundo/magicicada-client/remove-web into lp:magicicada-client

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
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.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks good! Thanks

review: Approve

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

Subscribers

People subscribed via source and target branches

to all changes: