Merge lp:~facundo/magicicada-client/kill-qt4 into lp:magicicada-client

Proposed by Facundo Batista
Status: Merged
Approved by: Natalia Bidart
Approved revision: 1439
Merged at revision: 1439
Proposed branch: lp:~facundo/magicicada-client/kill-qt4
Merge into: lp:magicicada-client
Diff against target: 3154 lines (+27/-2840)
24 files modified
bin/ubuntuone-proxy-tunnel (+0/-36)
contrib/check-reactor-import (+0/-76)
contrib/testing/testcase.py (+2/-16)
dependencies.txt (+0/-2)
run-tests (+2/-9)
run-tests.bat (+0/-2)
setup.py (+1/-1)
ubuntuone/proxy/__init__.py (+0/-27)
ubuntuone/proxy/common.py (+0/-73)
ubuntuone/proxy/logger.py (+0/-48)
ubuntuone/proxy/tests/__init__.py (+0/-156)
ubuntuone/proxy/tests/ssl/dummy.cert (+0/-19)
ubuntuone/proxy/tests/ssl/dummy.key (+0/-16)
ubuntuone/proxy/tests/test_tunnel_client.py (+0/-225)
ubuntuone/proxy/tests/test_tunnel_server.py (+0/-743)
ubuntuone/proxy/tunnel_client.py (+0/-202)
ubuntuone/proxy/tunnel_server.py (+0/-405)
ubuntuone/syncdaemon/action_queue.py (+3/-11)
ubuntuone/syncdaemon/tests/test_action_queue.py (+17/-57)
ubuntuone/syncdaemon/tests/test_tunnel_runner.py (+0/-185)
ubuntuone/syncdaemon/tunnel_runner.py (+0/-86)
ubuntuone/syncdaemon/utils.py (+2/-9)
ubuntuone/utils/gsettings.py (+0/-114)
ubuntuone/utils/tests/test_gsettings.py (+0/-322)
To merge this branch: bzr merge lp:~facundo/magicicada-client/kill-qt4
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+341428@code.launchpad.net

Commit message

Removed everything PyQt related, including the HTTP(S) Proxy.

Description of the change

Removed everything PyQt related, including the HTTP(S) Proxy.

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'bin/ubuntuone-proxy-tunnel'
--- bin/ubuntuone-proxy-tunnel 2015-09-29 21:05:26 +0000
+++ bin/ubuntuone-proxy-tunnel 1970-01-01 00:00:00 +0000
@@ -1,36 +0,0 @@
1#!/usr/bin/python
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Tunnel for proxy support."""
30
31import sys
32
33from ubuntuone.proxy.tunnel_server import main
34
35if __name__ == "__main__":
36 main(sys.argv)
370
=== removed file 'contrib/check-reactor-import'
--- contrib/check-reactor-import 2016-05-28 23:32:02 +0000
+++ contrib/check-reactor-import 1970-01-01 00:00:00 +0000
@@ -1,76 +0,0 @@
1#! /usr/bin/python
2#
3# Copyright (C) 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""A script that checks for unintended imports of twisted.internet.reactor."""
30
31# NOTE: the goal of this script is to avoid a bug that affects
32# ubuntuone-control-panel on windows and darwin. Those platforms use
33# the qt4reactor, and will break if the default reactor is installed
34# first. This can happen if a module used by control-panel imports reactor.
35# Only sub-modules that are not used by ubuntuone-control-panel can safely
36# import reactor at module-level.
37
38from __future__ import (unicode_literals, print_function)
39
40import __builtin__
41
42import os
43import sys
44import traceback
45
46sys.path.append(os.path.abspath(os.getcwd()))
47
48
49def fake_import(*args, **kwargs):
50 """A wrapper for __import__ that dies when importing reactor."""
51 imp_name_base = args[0]
52
53 if len(args) == 4 and args[3] is not None:
54 imp_names = ["{0}.{1}".format(imp_name_base, sm)
55 for sm in args[3]]
56 else:
57 imp_names = [imp_name_base]
58
59 for imp_name in imp_names:
60 if 'twisted.internet.reactor' == imp_name:
61 print("ERROR: should not import reactor here:")
62 traceback.print_stack()
63 sys.exit(1)
64
65 r = real_import(*args, **kwargs)
66 return r
67
68
69if __name__ == '__main__':
70
71 real_import = __builtin__.__import__
72 __builtin__.__import__ = fake_import
73
74 subs = ["", ".tools", ".logger"]
75 for module in ["ubuntuone.platform" + p for p in subs]:
76 m = __import__(module)
770
=== modified file 'contrib/testing/testcase.py'
--- contrib/testing/testcase.py 2016-09-17 01:06:23 +0000
+++ contrib/testing/testcase.py 2018-03-14 21:14:07 +0000
@@ -1,7 +1,7 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2#2#
3# Copyright 2009-2015 Canonical Ltd.3# Copyright 2009-2015 Canonical Ltd.
4# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)4# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
5#5#
6# This program is free software: you can redistribute it and/or modify it6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published7# under the terms of the GNU General Public License version 3, as published
@@ -41,7 +41,7 @@
41from collections import defaultdict41from collections import defaultdict
42from functools import wraps42from functools import wraps
4343
44from twisted.internet import defer, reactor44from twisted.internet import defer
45from twisted.trial.unittest import TestCase as TwistedTestCase45from twisted.trial.unittest import TestCase as TwistedTestCase
46from ubuntuone.devtools.testcases import skipIfOS46from ubuntuone.devtools.testcases import skipIfOS
47from zope.interface import implements47from zope.interface import implements
@@ -312,17 +312,6 @@
312 return defer.succeed(True)312 return defer.succeed(True)
313313
314314
315class FakeTunnelRunner(object):
316 """A fake proxy.tunnel_client.TunnelRunner."""
317
318 def __init__(self, *args):
319 """Fake a proxy tunnel."""
320
321 def get_client(self):
322 """Always return the reactor."""
323 return defer.succeed(reactor)
324
325
326class BaseTwistedTestCase(TwistedTestCase):315class BaseTwistedTestCase(TwistedTestCase):
327 """Base TestCase with helper methods to handle temp dir.316 """Base TestCase with helper methods to handle temp dir.
328317
@@ -332,7 +321,6 @@
332 makedirs(path): support read-only shares321 makedirs(path): support read-only shares
333 """322 """
334 MAX_FILENAME = 32 # some platforms limit lengths of filenames323 MAX_FILENAME = 32 # some platforms limit lengths of filenames
335 tunnel_runner_class = FakeTunnelRunner
336324
337 def mktemp(self, name='temp'):325 def mktemp(self, name='temp'):
338 """ Customized mktemp that accepts an optional name argument. """326 """ Customized mktemp that accepts an optional name argument. """
@@ -439,8 +427,6 @@
439 self.log = logging.getLogger("ubuntuone.SyncDaemon.TEST")427 self.log = logging.getLogger("ubuntuone.SyncDaemon.TEST")
440 self.log.info("starting test %s.%s", self.__class__.__name__,428 self.log.info("starting test %s.%s", self.__class__.__name__,
441 self._testMethodName)429 self._testMethodName)
442 self.patch(action_queue.tunnel_runner, "TunnelRunner",
443 self.tunnel_runner_class)
444430
445431
446class FakeMainTestCase(BaseTwistedTestCase):432class FakeMainTestCase(BaseTwistedTestCase):
447433
=== modified file 'dependencies.txt'
--- dependencies.txt 2017-01-07 18:51:07 +0000
+++ dependencies.txt 2018-03-14 21:14:07 +0000
@@ -6,6 +6,4 @@
6python-gi6python-gi
7python-protobuf7python-protobuf
8python-pyinotify8python-pyinotify
9python-qt4-dbus
10python-qt4reactor
11python-twisted9python-twisted
1210
=== modified file 'run-tests'
--- run-tests 2016-05-29 20:16:26 +0000
+++ run-tests 2018-03-14 21:14:07 +0000
@@ -1,6 +1,7 @@
1#! /bin/bash1#! /bin/bash
2#2#
3# Copyright 2012-2013 Canonical Ltd.3# Copyright 2012-2013 Canonical Ltd.
4# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4#5#
5# This program is free software: you can redistribute it and/or modify it6# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published7# under the terms of the GNU General Public License version 3, as published
@@ -27,8 +28,6 @@
27# version. If you delete this exception statement from all source28# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.29# files in the program, then also delete it here.
2930
30PROXY_TESTS_PATH="ubuntuone/proxy/tests"
31
32# Allow alternative python executable via environment variable. This is31# Allow alternative python executable via environment variable. This is
33# useful for virtualenv testing.32# useful for virtualenv testing.
34PYTHON=${PYTHON:-'python'}33PYTHON=${PYTHON:-'python'}
@@ -47,22 +46,16 @@
47if [ "$SYSNAME" == "Darwin" ]; then46if [ "$SYSNAME" == "Darwin" ]; then
48 IGNORE_FILES="test_linux.py,test_windows.py"47 IGNORE_FILES="test_linux.py,test_windows.py"
49 IGNORE_PATHS="ubuntuone/platform/tests/linux"48 IGNORE_PATHS="ubuntuone/platform/tests/linux"
50 REACTOR=qt4
51else49else
52 # Linux50 # Linux
53 IGNORE_FILES="test_darwin.py,test_fsevents_daemon.py,test_windows.py"51 IGNORE_FILES="test_darwin.py,test_fsevents_daemon.py,test_windows.py"
54 IGNORE_PATHS="ubuntuone/platform/tests/windows"52 IGNORE_PATHS="ubuntuone/platform/tests/windows"
55 REACTOR=gi
56fi53fi
5754
58echo "*** Running test suite for ""$MODULE"" ***"55echo "*** Running test suite for ""$MODULE"" ***"
59export SSL_CERTIFICATES_DIR=/etc/ssl/certs56export SSL_CERTIFICATES_DIR=/etc/ssl/certs
60$PYTHON ./setup.py build57$PYTHON ./setup.py build
61u1trial --reactor=$REACTOR -i "$IGNORE_FILES" -p "$IGNORE_PATHS,$PROXY_TESTS_PATH" "$MODULE"58u1trial -i "$IGNORE_FILES" -p "$IGNORE_PATHS" "$MODULE"
62echo "*** Running tests for ubuntuone-client-proxy ***"
63u1trial --reactor=qt4 -i "$IGNORE_FILES" -p "$IGNORE_PATHS" "$PROXY_TESTS_PATH"
64$PYTHON ./setup.py clean59$PYTHON ./setup.py clean
65rm -rf _trial_temp60rm -rf _trial_temp
66rm -rf build61rm -rf build
67
68$PYTHON contrib/check-reactor-import
6962
=== modified file 'run-tests.bat'
--- run-tests.bat 2013-06-10 19:27:21 +0000
+++ run-tests.bat 2018-03-14 21:14:07 +0000
@@ -87,8 +87,6 @@
87ECHO Performing style checks...87ECHO Performing style checks...
88"%LINTPATH%"88"%LINTPATH%"
8989
90"%PYTHONEXEPATH%" contrib\check-reactor-import
91
92:: if pep8 is not present, move to the end90:: if pep8 is not present, move to the end
93IF EXIST "%PEP8PATH%" (91IF EXIST "%PEP8PATH%" (
94 "%PEP8PATH%" --repeat ubuntuone92 "%PEP8PATH%" --repeat ubuntuone
9593
=== modified file 'setup.py'
--- setup.py 2016-08-06 20:02:14 +0000
+++ setup.py 2018-03-14 21:14:07 +0000
@@ -1,6 +1,7 @@
1#!/usr/bin/python1#!/usr/bin/python
2#2#
3# Copyright 2013 Canonical Ltd.3# Copyright 2013 Canonical Ltd.
4# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4#5#
5# This program is free software: you can redistribute it and/or modify it6# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published7# under the terms of the GNU General Public License version 3, as published
@@ -203,7 +204,6 @@
203]204]
204205
205libexec_scripts = [206libexec_scripts = [
206 'bin/ubuntuone-proxy-tunnel',
207 'bin/ubuntuone-syncdaemon',207 'bin/ubuntuone-syncdaemon',
208]208]
209209
210210
=== removed directory 'ubuntuone/proxy'
=== removed file 'ubuntuone/proxy/__init__.py'
--- ubuntuone/proxy/__init__.py 2016-05-29 00:50:05 +0000
+++ ubuntuone/proxy/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,27 +0,0 @@
1# Copyright 2012 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# In addition, as a special exception, the copyright holders give
16# permission to link the code of portions of this program with the
17# OpenSSL library under certain conditions as described in each
18# individual source file, and distribute linked combinations
19# including the two.
20# You must obey the GNU General Public License in all respects
21# for all of the code used other than OpenSSL. If you modify
22# file(s) with this exception, you may extend this exception to your
23# version of the file(s), but you are not obligated to do so. If you
24# do not wish to do so, delete this exception statement from your
25# version. If you delete this exception statement from all source
26# files in the program, then also delete it here.
27"""Proxy support."""
280
=== removed file 'ubuntuone/proxy/common.py'
--- ubuntuone/proxy/common.py 2012-04-09 20:08:42 +0000
+++ ubuntuone/proxy/common.py 1970-01-01 00:00:00 +0000
@@ -1,73 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Common classes to the tunnel client and server."""
30
31from twisted.protocols import basic
32
33CRLF = "\r\n"
34TUNNEL_PORT_LABEL = "Tunnel port"
35TUNNEL_COOKIE_LABEL = "Tunnel cookie"
36TUNNEL_COOKIE_HEADER = "Proxy-Tunnel-Cookie"
37
38
39class BaseTunnelProtocol(basic.LineReceiver):
40 """CONNECT base protocol for tunnelling connections."""
41
42 delimiter = CRLF
43
44 def __init__(self):
45 """Initialize this protocol."""
46 self._first_line = True
47 self.received_headers = []
48
49 def header_line(self, line):
50 """Handle each header line received."""
51 key, value = line.split(":", 1)
52 value = value.strip()
53 self.received_headers.append((key, value))
54
55 def lineReceived(self, line):
56 """Process a line in the header."""
57 if self._first_line:
58 self._first_line = False
59 self.handle_first_line(line)
60 else:
61 if line:
62 self.header_line(line)
63 else:
64 self.setRawMode()
65 self.headers_done()
66
67 def remote_disconnected(self):
68 """The remote end closed the connection."""
69 self.transport.loseConnection()
70
71 def format_headers(self, headers):
72 """Format some headers as a few response lines."""
73 return "".join("%s: %s" % item + CRLF for item in headers.items())
740
=== removed file 'ubuntuone/proxy/logger.py'
--- ubuntuone/proxy/logger.py 2012-06-21 18:58:50 +0000
+++ ubuntuone/proxy/logger.py 1970-01-01 00:00:00 +0000
@@ -1,48 +0,0 @@
1# ubuntuone.syncdaemon.logger - logging utilities
2#
3# Copyright 2009-2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""SyncDaemon logging utilities and config."""
30
31import logging
32import os
33
34from ubuntuone.logger import (
35 basic_formatter,
36 CustomRotatingFileHandler,
37)
38
39from ubuntuone.platform.logger import ubuntuone_log_dir
40
41
42LOGFILENAME = os.path.join(ubuntuone_log_dir, 'proxy.log')
43logger = logging.getLogger("ubuntuone.proxy")
44logger.setLevel(logging.DEBUG)
45handler = CustomRotatingFileHandler(filename=LOGFILENAME)
46handler.setFormatter(basic_formatter)
47handler.setLevel(logging.DEBUG)
48logger.addHandler(handler)
490
=== removed directory 'ubuntuone/proxy/tests'
=== removed file 'ubuntuone/proxy/tests/__init__.py'
--- ubuntuone/proxy/tests/__init__.py 2016-06-01 18:28:19 +0000
+++ ubuntuone/proxy/tests/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,156 +0,0 @@
1# Copyright 2012 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.
14#
15# In addition, as a special exception, the copyright holders give
16# permission to link the code of portions of this program with the
17# OpenSSL library under certain conditions as described in each
18# individual source file, and distribute linked combinations
19# including the two.
20# You must obey the GNU General Public License in all respects
21# for all of the code used other than OpenSSL. If you modify
22# file(s) with this exception, you may extend this exception to your
23# version of the file(s), but you are not obligated to do so. If you
24# do not wish to do so, delete this exception statement from your
25# version. If you delete this exception statement from all source
26# files in the program, then also delete it here.
27"""Tests for the Magicicada proxy support."""
28
29from os import path
30from StringIO import StringIO
31
32from twisted.application import internet, service
33from twisted.internet import defer, ssl
34from twisted.web import http, resource, server
35
36SAMPLE_CONTENT = "hello world!"
37SIMPLERESOURCE = "simpleresource"
38DUMMY_KEY_FILENAME = "dummy.key"
39DUMMY_CERT_FILENAME = "dummy.cert"
40FAKE_COOKIE = "fa:ke:co:ok:ie"
41
42
43class SaveHTTPChannel(http.HTTPChannel):
44 """A save protocol to be used in tests."""
45
46 protocolInstance = None
47
48 def connectionMade(self):
49 """Keep track of the given protocol."""
50 SaveHTTPChannel.protocolInstance = self
51 http.HTTPChannel.connectionMade(self)
52
53
54class SaveSite(server.Site):
55 """A site that let us know when it's closed."""
56
57 protocol = SaveHTTPChannel
58
59 def __init__(self, *args, **kwargs):
60 """Create a new instance."""
61 server.Site.__init__(self, *args, **kwargs)
62 self.timeOut = None
63
64
65class BaseMockWebServer(object):
66 """A mock webserver for testing"""
67
68 def __init__(self):
69 """Start up this instance."""
70 self.root = self.get_root_resource()
71 self.site = SaveSite(self.root)
72 application = service.Application('web')
73 self.service_collection = service.IServiceCollection(application)
74 self.tcpserver = internet.TCPServer(0, self.site)
75 self.tcpserver.setServiceParent(self.service_collection)
76 self.sslserver = internet.SSLServer(0, self.site, self.get_context())
77 self.sslserver.setServiceParent(self.service_collection)
78 self.service_collection.startService()
79
80 def get_dummy_path(self, filename):
81 """Path pointing at the dummy certificate files."""
82 base_path = path.dirname(__file__)
83 return path.join(base_path, "ssl", filename)
84
85 def get_context(self):
86 """Return an ssl context."""
87 key_path = self.get_dummy_path(DUMMY_KEY_FILENAME)
88 cert_path = self.get_dummy_path(DUMMY_CERT_FILENAME)
89 return ssl.DefaultOpenSSLContextFactory(key_path, cert_path)
90
91 def get_root_resource(self):
92 """Get the root resource with all the children."""
93 raise NotImplementedError
94
95 def get_iri(self):
96 """Build the iri for this mock server."""
97 port_num = self.tcpserver._port.getHost().port
98 return u"http://0.0.0.0:%d/" % port_num
99
100 def get_ssl_iri(self):
101 """Build the iri for the ssl mock server."""
102 port_num = self.sslserver._port.getHost().port
103 return u"https://0.0.0.0:%d/" % port_num
104
105 def stop(self):
106 """Shut it down."""
107 if self.site.protocol.protocolInstance:
108 self.site.protocol.protocolInstance.timeoutConnection()
109 return self.service_collection.stopService()
110
111
112class SimpleResource(resource.Resource):
113 """A simple web resource."""
114
115 def __init__(self):
116 """Initialize this mock resource."""
117 resource.Resource.__init__(self)
118 self.rendered = defer.Deferred()
119
120 def render_GET(self, request):
121 """Make a bit of html out of the resource's content."""
122 if not self.rendered.called:
123 self.rendered.callback(None)
124 return SAMPLE_CONTENT
125
126
127class MockWebServer(BaseMockWebServer):
128 """A mock webserver."""
129
130 def __init__(self):
131 """Initialize this mock server."""
132 self.simple_resource = SimpleResource()
133 super(MockWebServer, self).__init__()
134
135 def get_root_resource(self):
136 """Get the root resource with all the children."""
137 root = resource.Resource()
138 root.putChild(SIMPLERESOURCE, self.simple_resource)
139 return root
140
141
142class FakeTransport(StringIO):
143 """A fake transport that stores everything written to it."""
144
145 connected = True
146 disconnecting = False
147 cookie = None
148
149 def loseConnection(self):
150 """Mark the connection as lost."""
151 self.connected = False
152 self.disconnecting = True
153
154 def getPeer(self):
155 """Return the peer IAddress."""
156 return None
1570
=== removed directory 'ubuntuone/proxy/tests/ssl'
=== removed file 'ubuntuone/proxy/tests/ssl/dummy.cert'
--- ubuntuone/proxy/tests/ssl/dummy.cert 2012-02-23 23:58:33 +0000
+++ ubuntuone/proxy/tests/ssl/dummy.cert 1970-01-01 00:00:00 +0000
@@ -1,19 +0,0 @@
1-----BEGIN CERTIFICATE-----
2MIIDEDCCAnmgAwIBAgIJAM/bIJ77awBCMA0GCSqGSIb3DQEBBQUAMIGgMQswCQYD
3VQQGEwJBUjETMBEGA1UECAwKRmFrZSBTdGF0ZTESMBAGA1UEBwwJRmFrZSBDaXR5
4MRUwEwYDVQQKDAxGYWtlIENvbXBhbnkxFjAUBgNVBAsMDUZha2UgRGl2aXNpb24x
5EjAQBgNVBAMMCUZha2UgTmFtZTElMCMGCSqGSIb3DQEJARYWZmFrZUBlbWFpbC5h
6ZGRyZXNzLm5vdDAeFw0xMjAyMjIxOTI0MjBaFw0yMjAyMjMxOTI0MjBaMIGgMQsw
7CQYDVQQGEwJBUjETMBEGA1UECAwKRmFrZSBTdGF0ZTESMBAGA1UEBwwJRmFrZSBD
8aXR5MRUwEwYDVQQKDAxGYWtlIENvbXBhbnkxFjAUBgNVBAsMDUZha2UgRGl2aXNp
9b24xEjAQBgNVBAMMCUZha2UgTmFtZTElMCMGCSqGSIb3DQEJARYWZmFrZUBlbWFp
10bC5hZGRyZXNzLm5vdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0hbliGty
11HwfZixU609UHBQdbfO+oObrPIrIawWX5FxD6KhX4ei23idmpyYEcXLK4ivNlT4dW
1227bvhtpf6/FBbu9e1YdwcdDNoXajr9Ia4NZJyANgo9b5UIsnyTc45NlnpZgRg5zc
13Oz7Vwwr4qf6r1ljK/I2mAO7rlpH5Ak9J+RkCAwEAAaNQME4wHQYDVR0OBBYEFLwr
14ps/JLNcfpSuuylMnkvImVvkgMB8GA1UdIwQYMBaAFLwrps/JLNcfpSuuylMnkvIm
15VvkgMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAWYDBAr0MgpnBxIne
16WRz8MX0/c7IqrEuZCYMSGnU7PoX3GdNk1Lkif1ufELKSoG8jY16CDgEl26GPxA1k
17Tho7MWSLikLbuQYJs2saF9by0Y/Mrau0auxEnpHZ7pkybeKFnrIqiNKvTVMnjo5T
18FMET5qEOKKvp9IOnezCYX1nYXyY=
19-----END CERTIFICATE-----
200
=== removed file 'ubuntuone/proxy/tests/ssl/dummy.key'
--- ubuntuone/proxy/tests/ssl/dummy.key 2012-02-23 23:58:33 +0000
+++ ubuntuone/proxy/tests/ssl/dummy.key 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1-----BEGIN PRIVATE KEY-----
2MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANIW5Yhrch8H2YsV
3OtPVBwUHW3zvqDm6zyKyGsFl+RcQ+ioV+Hott4nZqcmBHFyyuIrzZU+HVtu274ba
4X+vxQW7vXtWHcHHQzaF2o6/SGuDWScgDYKPW+VCLJ8k3OOTZZ6WYEYOc3Ds+1cMK
5+Kn+q9ZYyvyNpgDu65aR+QJPSfkZAgMBAAECgYBSxFh7TTExjmsjAyMg700LqyFc
68CHLVJBkL9ygkqb2cmbMC8nPgJFNSqY8T5Q35OUVQNyJ31zVxJVLAF9H2c0Xy48K
7IkbS/hntyqlJYK1yfTbTHkDiweToE3Lm+55Do1TX04AyvBrwA1O/jNGi4xIlUEAy
81Bs8MrJ1E/j/XDn9/QJBAOuhPTgG3F7bKuBrQzv98CvC5o2Txf3vLY8nL8V24b3l
9XgqzkDLhUxReBmmkGxZfKAju3+gXFvGGpbP7V8zShg8CQQDkQGs7kArFq/KR/GCh
10CAmJaDWy4LJkSqzDHoJbTrS7YuqN6X6mW1xPRnWpYSxae38fJsCpG3Vq8Mv1Zl32
11VPZXAkEAsAeE9JYri7GwFngLgoXzJr4z/xCmmU5VetyLk7l8a6Eu4E/FKj2rE0wq
12/kDa+5ubDRFntLuLKGSu5gafUST1gQJABhdmBTfp4a6eEaFPntyNDJq4XCa8/Ao2
13JBrrVa57Ckkwg0sI8z2a8A6sUzHhsiR7lwQ8vgaakpkMiGcL+Of5jwJAA/qX3PW+
149JXbjWxpgh7FHnZJNRZ8xSe47REGA7qS/nIlV9iRuf/9M+k3A5VqitfFxrjPwSyI
15rvKTYkk13dL4hg==
16-----END PRIVATE KEY-----
170
=== removed file 'ubuntuone/proxy/tests/test_tunnel_client.py'
--- ubuntuone/proxy/tests/test_tunnel_client.py 2016-06-01 18:28:19 +0000
+++ ubuntuone/proxy/tests/test_tunnel_client.py 1970-01-01 00:00:00 +0000
@@ -1,225 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Tests for the proxy tunnel."""
30
31from twisted.internet import defer, protocol, ssl
32from twisted.trial.unittest import TestCase
33from twisted.web import client
34
35from ubuntuone.devtools.testcases.squid import SquidTestCase
36
37from ubuntuone.proxy.tests import (
38 FakeTransport,
39 FAKE_COOKIE,
40 MockWebServer,
41 SAMPLE_CONTENT,
42 SIMPLERESOURCE,
43)
44from ubuntuone.proxy import tunnel_client
45from ubuntuone.proxy.tunnel_client import CRLF, TunnelClient
46from ubuntuone.proxy.tunnel_server import TunnelServer
47
48
49FAKE_HEADER = (
50 "HTTP/1.0 200 Connected!" + CRLF +
51 "Header1: value1" + CRLF +
52 "Header2: value2" + CRLF +
53 CRLF
54)
55
56
57class SavingProtocol(protocol.Protocol):
58 """A protocol that saves all that it receives."""
59
60 def __init__(self):
61 """Initialize this protocol."""
62 self.saved_data = None
63
64 def connectionMade(self):
65 """The connection was made, start saving."""
66 self.saved_data = []
67
68 def dataReceived(self, data):
69 """Save the data received."""
70 self.saved_data.append(data)
71
72 @property
73 def content(self):
74 """All the content so far."""
75 return "".join(self.saved_data)
76
77
78class TunnelClientProtocolTestCase(TestCase):
79 """Tests for the client side tunnel protocol."""
80
81 timeout = 3
82
83 @defer.inlineCallbacks
84 def setUp(self):
85 """Initialize this testcase."""
86 yield super(TunnelClientProtocolTestCase, self).setUp()
87 self.host, self.port = "9.9.9.9", 8765
88 fake_addr = object()
89 self.cookie = FAKE_COOKIE
90 self.other_proto = SavingProtocol()
91 other_factory = protocol.ClientFactory()
92 other_factory.buildProtocol = lambda _addr: self.other_proto
93 tunnel_client_factory = tunnel_client.TunnelClientFactory(
94 self.host, self.port, other_factory, self.cookie)
95 tunnel_client_proto = tunnel_client_factory.buildProtocol(fake_addr)
96 tunnel_client_proto.transport = FakeTransport()
97 tunnel_client_proto.connectionMade()
98 self.tunnel_client_proto = tunnel_client_proto
99
100 def test_sends_connect_request(self):
101 """Sends the expected CONNECT request."""
102 expected = tunnel_client.METHOD_LINE % (self.host, self.port)
103 written = self.tunnel_client_proto.transport.getvalue()
104 first_line = written.split(CRLF)[0]
105 self.assertEqual(first_line + CRLF, expected)
106 self.assertTrue(written.endswith(CRLF * 2),
107 "Ends with a double CRLF")
108
109 def test_sends_cookie_header(self):
110 """Sends the expected cookie header."""
111 expected = "%s: %s" % (tunnel_client.TUNNEL_COOKIE_HEADER, self.cookie)
112 written = self.tunnel_client_proto.transport.getvalue()
113 headers = written.split(CRLF)[1:]
114 self.assertIn(expected, headers)
115
116 def test_handles_successful_connection(self):
117 """A successful connection is handled."""
118 self.tunnel_client_proto.dataReceived(FAKE_HEADER)
119 self.assertEqual(self.tunnel_client_proto.status_code, "200")
120
121 def test_protocol_is_switched(self):
122 """The protocol is switched after the headers are received."""
123 expected = (SAMPLE_CONTENT + CRLF) * 2
124 self.tunnel_client_proto.dataReceived(FAKE_HEADER + SAMPLE_CONTENT)
125 self.other_proto.dataReceived(CRLF + SAMPLE_CONTENT + CRLF)
126 self.assertEqual(self.other_proto.content, expected)
127
128
129class FakeOtherFactory(object):
130 """A fake factory."""
131
132 def __init__(self):
133 """Initialize this fake."""
134 self.started_called = None
135 self.failed_called = None
136 self.lost_called = None
137
138 def startedConnecting(self, *args):
139 """Store the call."""
140 self.started_called = args
141
142 def clientConnectionFailed(self, *args):
143 """Store the call."""
144 self.failed_called = args
145
146 def clientConnectionLost(self, *args):
147 """Store the call."""
148 self.lost_called = args
149
150
151class TunnelClientFactoryTestCase(TestCase):
152 """Tests for the TunnelClientFactory."""
153
154 def test_forwards_started(self):
155 """The factory forwards the startedConnecting call."""
156 fake_other_factory = FakeOtherFactory()
157 tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory,
158 FAKE_COOKIE)
159 fake_connector = object()
160 tcf.startedConnecting(fake_connector)
161 self.assertEqual(fake_other_factory.started_called, (fake_connector,))
162
163 def test_forwards_failed(self):
164 """The factory forwards the clientConnectionFailed call."""
165 fake_reason = object()
166 fake_other_factory = FakeOtherFactory()
167 tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory,
168 FAKE_COOKIE)
169 fake_connector = object()
170 tcf.clientConnectionFailed(fake_connector, fake_reason)
171 self.assertEqual(fake_other_factory.failed_called,
172 (fake_connector, fake_reason))
173
174 def test_forwards_lost(self):
175 """The factory forwards the clientConnectionLost call."""
176 fake_reason = object()
177 fake_other_factory = FakeOtherFactory()
178 tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory,
179 FAKE_COOKIE)
180 fake_connector = object()
181 tcf.clientConnectionLost(fake_connector, fake_reason)
182 self.assertEqual(fake_other_factory.lost_called,
183 (fake_connector, fake_reason))
184
185
186class TunnelClientTestCase(SquidTestCase):
187 """Test the client for the tunnel."""
188
189 timeout = 3
190
191 @defer.inlineCallbacks
192 def setUp(self):
193 """Initialize this testcase."""
194 yield super(TunnelClientTestCase, self).setUp()
195 self.ws = MockWebServer()
196 self.addCleanup(self.ws.stop)
197 self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
198 self.dest_ssl_url = (
199 self.ws.get_ssl_iri().encode("utf-8") + SIMPLERESOURCE)
200 self.cookie = FAKE_COOKIE
201 self.tunnel_server = TunnelServer(self.cookie)
202 self.addCleanup(self.tunnel_server.shutdown)
203
204 @defer.inlineCallbacks
205 def test_connects_right(self):
206 """Uses the CONNECT method on the tunnel."""
207 tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port,
208 self.cookie)
209 factory = client.HTTPClientFactory(self.dest_url)
210 scheme, host, port, path = client._parse(self.dest_url)
211 tunnel_client.connectTCP(host, port, factory)
212 result = yield factory.deferred
213 self.assertEqual(result, SAMPLE_CONTENT)
214
215 @defer.inlineCallbacks
216 def test_starts_tls_connection(self):
217 """TLS is started after connecting; control passed to the client."""
218 tunnel_client = TunnelClient(
219 "0.0.0.0", self.tunnel_server.port, self.cookie)
220 factory = client.HTTPClientFactory(self.dest_ssl_url)
221 scheme, host, port, path = client._parse(self.dest_ssl_url)
222 context_factory = ssl.ClientContextFactory()
223 tunnel_client.connectSSL(host, port, factory, context_factory)
224 result = yield factory.deferred
225 self.assertEqual(result, SAMPLE_CONTENT)
2260
=== removed file 'ubuntuone/proxy/tests/test_tunnel_server.py'
--- ubuntuone/proxy/tests/test_tunnel_server.py 2016-06-01 18:28:19 +0000
+++ ubuntuone/proxy/tests/test_tunnel_server.py 1970-01-01 00:00:00 +0000
@@ -1,743 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012-2013 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Tests for the proxy tunnel."""
30
31from StringIO import StringIO
32from urlparse import urlparse
33
34from twisted.internet import defer, protocol, reactor
35from twisted.trial.unittest import TestCase
36from PyQt4.QtCore import QCoreApplication
37from PyQt4.QtNetwork import QAuthenticator
38from ubuntuone.devtools.testcases import skipIfOS
39from ubuntuone.devtools.testcases.squid import SquidTestCase
40
41from ubuntuone.proxy.tests import (
42 FakeTransport,
43 FAKE_COOKIE,
44 MockWebServer,
45 SAMPLE_CONTENT,
46 SIMPLERESOURCE,
47)
48from ubuntuone.proxy import tunnel_server
49from ubuntuone.proxy.tunnel_server import CRLF
50
51
52FAKE_SESSION_TEMPLATE = (
53 "CONNECT %s HTTP/1.0" + CRLF +
54 "Header1: value1" + CRLF +
55 "Header2: value2" + CRLF +
56 tunnel_server.TUNNEL_COOKIE_HEADER + ": %s" + CRLF +
57 CRLF +
58 "GET %s HTTP/1.0" + CRLF + CRLF
59)
60
61FAKE_SETTINGS = {
62 "http": {
63 "host": "myhost",
64 "port": 8888,
65 }
66}
67
68FAKE_AUTH_SETTINGS = {
69 "http": {
70 "host": "myhost",
71 "port": 8888,
72 "username": "fake_user",
73 "password": "fake_password",
74 }
75}
76
77SAMPLE_HOST = "samplehost.com"
78SAMPLE_PORT = 443
79
80FAKE_CREDS = {
81 "username": "rhea",
82 "password": "caracolcaracola",
83}
84
85
86class DisconnectingProtocol(protocol.Protocol):
87 """A protocol that just disconnects."""
88
89 def connectionMade(self):
90 """Upon connecting: just disconnect."""
91 self.transport.loseConnection()
92
93
94class DisconnectingClientFactory(protocol.ClientFactory):
95 """A factory that fires a deferred on connection."""
96
97 def __init__(self):
98 """Initialize this instance."""
99 self.connected = defer.Deferred()
100
101 def buildProtocol(self, addr):
102 """The connection was made."""
103 proto = DisconnectingProtocol()
104 if not self.connected.called:
105 self.connected.callback(proto)
106 return proto
107
108
109class FakeProtocol(protocol.Protocol):
110 """A protocol that forwards some data."""
111
112 def __init__(self, factory, data):
113 """Initialize this fake."""
114 self.factory = factory
115 self.data = data
116 self.received_data = []
117
118 def connectionMade(self):
119 """Upon connection: send the stored data."""
120 self.transport.write(self.data)
121
122 def dataReceived(self, data):
123 """Some data was received."""
124 self.received_data.append(data)
125
126 def connectionLost(self, reason):
127 """The connection was lost, return the response."""
128 response = "".join(self.received_data)
129 if not self.factory.response.called:
130 self.factory.response.callback(response)
131
132
133class FakeClientFactory(protocol.ClientFactory):
134 """A factory that forwards some data to the protocol."""
135
136 def __init__(self, data):
137 """Initialize this fake."""
138 self.data = data
139 self.response = defer.Deferred()
140
141 def buildProtocol(self, addr):
142 """The connection was made."""
143 return FakeProtocol(self, self.data)
144
145
146class TunnelIntegrationTestCase(SquidTestCase):
147 """Basic tunnel integration tests."""
148
149 timeout = 3
150
151 @defer.inlineCallbacks
152 def setUp(self):
153 """Initialize this testcase."""
154 yield super(TunnelIntegrationTestCase, self).setUp()
155 self.ws = MockWebServer()
156 self.addCleanup(self.ws.stop)
157 self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
158 self.cookie = FAKE_COOKIE
159 self.tunnel_server = tunnel_server.TunnelServer(self.cookie)
160 self.addCleanup(self.tunnel_server.shutdown)
161
162 def test_init(self):
163 """The tunnel is started."""
164 self.assertNotEqual(self.tunnel_server.port, 0)
165
166 @defer.inlineCallbacks
167 def test_accepts_connections(self):
168 """The tunnel accepts incoming connections."""
169 ncf = DisconnectingClientFactory()
170 reactor.connectTCP("0.0.0.0", self.tunnel_server.port, ncf)
171 yield ncf.connected
172
173 @defer.inlineCallbacks
174 def test_complete_connection(self):
175 """Test from the tunnel server down."""
176 url = urlparse(self.dest_url)
177 fake_session = FAKE_SESSION_TEMPLATE % (
178 url.netloc, self.cookie, url.path)
179 client = FakeClientFactory(fake_session)
180 reactor.connectTCP("0.0.0.0", self.tunnel_server.port, client)
181 response = yield client.response
182 self.assertIn(SAMPLE_CONTENT, response)
183
184
185class FakeClient(object):
186 """A fake destination client."""
187
188 protocol = None
189 connection_result = defer.succeed(True)
190 credentials = None
191 check_credentials = False
192 proxy_domain = None
193
194 def connect(self, hostport):
195 """Establish a connection with the other end."""
196 if (self.check_credentials and
197 self.protocol.proxy_credentials != FAKE_CREDS):
198 self.proxy_domain = "fake domain"
199 return defer.fail(tunnel_server.ProxyAuthenticationError())
200 return self.connection_result
201
202 def write(self, data):
203 """Write some data to the other end."""
204 if data == 'GET /simpleresource HTTP/1.0\r\n\r\n':
205 self.protocol.transport.write(SAMPLE_CONTENT)
206
207 def stop(self):
208 """Stop this fake client."""
209
210 def close(self):
211 """Reset this client."""
212
213
214class ServerTunnelProtocolTestCase(SquidTestCase):
215 """Tests for the ServerTunnelProtocol."""
216
217 @defer.inlineCallbacks
218 def setUp(self):
219 """Initialize this test instance."""
220 yield super(ServerTunnelProtocolTestCase, self).setUp()
221 self.ws = MockWebServer()
222 self.addCleanup(self.ws.stop)
223 self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
224 self.transport = FakeTransport()
225 self.transport.cookie = FAKE_COOKIE
226 self.fake_client = FakeClient()
227 self.proto = tunnel_server.ServerTunnelProtocol(
228 lambda _: self.fake_client)
229 self.fake_client.protocol = self.proto
230 self.proto.transport = self.transport
231 self.cookie_line = "%s: %s" % (tunnel_server.TUNNEL_COOKIE_HEADER,
232 FAKE_COOKIE)
233
234 def test_broken_request(self):
235 """Broken request."""
236 self.proto.dataReceived("Broken request." + CRLF)
237 self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 400 "),
238 "A broken request must fail.")
239
240 def test_wrong_method(self):
241 """Wrong method."""
242 self.proto.dataReceived("GET http://slashdot.org HTTP/1.0" + CRLF)
243 self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 405 "),
244 "Using a wrong method fails.")
245
246 def test_invalid_http_version(self):
247 """Invalid HTTP version."""
248 self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.1" + CRLF)
249 self.assertTrue(self.transport.getvalue().startswith("HTTP/1.0 505 "),
250 "Invalid http version is not allowed.")
251
252 def test_connection_is_established(self):
253 """The response code is sent."""
254 expected = "HTTP/1.0 200 Proxy connection established" + CRLF
255 self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
256 self.cookie_line + CRLF * 2)
257 self.assertTrue(self.transport.getvalue().startswith(expected),
258 "First line must be the response status")
259
260 def test_connection_fails(self):
261 """The connection to the other end fails, and it's handled."""
262 error = tunnel_server.ConnectionError()
263 self.patch(self.fake_client, "connection_result", defer.fail(error))
264 expected = "HTTP/1.0 500 Connection error" + CRLF
265 self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
266 self.cookie_line + CRLF * 2)
267 self.assertTrue(self.transport.getvalue().startswith(expected),
268 "The connection should fail at this point.")
269
270 def test_headers_stored(self):
271 """The request headers are stored."""
272 expected = [
273 ("Header1", "value1"),
274 ("Header2", "value2"),
275 ]
276 self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF +
277 "Header1: value1" + CRLF +
278 "Header2: value2" + CRLF + CRLF)
279 self.assertEqual(self.proto.received_headers, expected)
280
281 def test_cookie_header_present(self):
282 """The cookie header must be present."""
283 self.proto.received_headers = [
284 (tunnel_server.TUNNEL_COOKIE_HEADER, FAKE_COOKIE),
285 ]
286 self.proto.verify_cookie()
287
288 def test_cookie_header_absent(self):
289 """The tunnel should refuse connections without the cookie."""
290 self.proto.received_headers = []
291 exception = self.assertRaises(tunnel_server.ConnectionError,
292 self.proto.verify_cookie)
293 self.assertEqual(exception.code, 418)
294
295 def test_successful_connect(self):
296 """A successful connect thru the tunnel."""
297 url = urlparse(self.dest_url)
298 data = FAKE_SESSION_TEMPLATE % (url.netloc, self.transport.cookie,
299 url.path)
300 self.proto.dataReceived(data)
301 lines = self.transport.getvalue().split(CRLF)
302 self.assertEqual(lines[-1], SAMPLE_CONTENT)
303
304 def test_header_split(self):
305 """Test a header with many colons."""
306 self.proto.header_line("key: host:port")
307 self.assertIn("key", dict(self.proto.received_headers))
308
309 @defer.inlineCallbacks
310 def test_keyring_credentials_are_retried(self):
311 """Wrong credentials are retried with values from keyring."""
312 self.fake_client.check_credentials = True
313 self.patch(self.proto, "verify_cookie", lambda: None)
314 self.patch(self.proto, "error_response",
315 lambda code, desc: self.fail(desc))
316 self.proto.proxy_domain = "xxx"
317 self.patch(tunnel_server.Keyring, "get_credentials",
318 lambda _, domain: defer.succeed(FAKE_CREDS))
319 yield self.proto.headers_done()
320
321 def test_creds_are_not_logged(self):
322 """The proxy credentials are not logged."""
323 log = []
324 self.patch(tunnel_server.logger, "info",
325 lambda text, *args: log.append(text % args))
326 proxy = tunnel_server.build_proxy(FAKE_AUTH_SETTINGS)
327 authenticator = QAuthenticator()
328 username = FAKE_AUTH_SETTINGS["http"]["username"]
329 password = FAKE_AUTH_SETTINGS["http"]["password"]
330 self.proto.proxy_credentials = {
331 "username": username,
332 "password": password,
333 }
334 self.proto.proxy_domain = proxy.hostName()
335
336 self.proto.proxy_auth_required(proxy, authenticator)
337
338 for line in log:
339 self.assertNotIn(username, line)
340 self.assertNotIn(password, line)
341
342
343class FakeServerTunnelProtocol(object):
344 """A fake ServerTunnelProtocol."""
345
346 def __init__(self):
347 """Initialize this fake tunnel."""
348 self.response_received = defer.Deferred()
349 self.proxy_credentials = None
350
351 def response_data_received(self, data):
352 """Fire the response deferred."""
353 if not self.response_received.called:
354 self.response_received.callback(data)
355
356 def remote_disconnected(self):
357 """The remote server disconnected."""
358
359 def proxy_auth_required(self, proxy, authenticator):
360 """Proxy credentials are needed."""
361 if self.proxy_credentials:
362 authenticator.setUser(self.proxy_credentials["username"])
363 authenticator.setPassword(self.proxy_credentials["password"])
364
365
366class BuildProxyTestCase(TestCase):
367 """Tests for the build_proxy function."""
368
369 def test_socks_is_preferred(self):
370 """Socks overrides all protocols."""
371 settings = {
372 "http": {"host": "httphost", "port": 3128},
373 "https": {"host": "httpshost", "port": 3129},
374 "socks": {"host": "sockshost", "port": 1080},
375 }
376 proxy = tunnel_server.build_proxy(settings)
377 self.assertEqual(proxy.type(), proxy.Socks5Proxy)
378 self.assertEqual(proxy.hostName(), "sockshost")
379 self.assertEqual(proxy.port(), 1080)
380
381 def test_https_beats_http(self):
382 """HTTPS wins over HTTP, since all of SD traffic is https."""
383 settings = {
384 "http": {"host": "httphost", "port": 3128},
385 "https": {"host": "httpshost", "port": 3129},
386 }
387 proxy = tunnel_server.build_proxy(settings)
388 self.assertEqual(proxy.type(), proxy.HttpProxy)
389 self.assertEqual(proxy.hostName(), "httpshost")
390 self.assertEqual(proxy.port(), 3129)
391
392 def test_http_if_no_other_choice(self):
393 """Finally, we use the host configured for HTTP."""
394 settings = {
395 "http": {"host": "httphost", "port": 3128},
396 }
397 proxy = tunnel_server.build_proxy(settings)
398 self.assertEqual(proxy.type(), proxy.HttpProxy)
399 self.assertEqual(proxy.hostName(), "httphost")
400 self.assertEqual(proxy.port(), 3128)
401
402 def test_use_noproxy_as_fallback(self):
403 """If nothing useful, revert to no proxy."""
404 settings = {}
405 proxy = tunnel_server.build_proxy(settings)
406 self.assertEqual(proxy.type(), proxy.DefaultProxy)
407
408
409class RemoteSocketTestCase(SquidTestCase):
410 """Tests for the client that connects to the other side."""
411
412 timeout = 3
413
414 def get_proxy_settings(self):
415 return {}
416
417 @defer.inlineCallbacks
418 def setUp(self):
419 """Initialize this testcase."""
420 yield super(RemoteSocketTestCase, self).setUp()
421 self.ws = MockWebServer()
422 self.addCleanup(self.ws.stop)
423 self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE
424
425 self.addCleanup(tunnel_server.QNetworkProxy.setApplicationProxy,
426 tunnel_server.QNetworkProxy.applicationProxy())
427 settings = {"http": self.get_proxy_settings()}
428 proxy = tunnel_server.build_proxy(settings)
429 tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
430
431 def test_invalid_port(self):
432 """A request with an invalid port fails with a 400."""
433 protocol = tunnel_server.ServerTunnelProtocol(
434 tunnel_server.RemoteSocket)
435 protocol.transport = FakeTransport()
436 protocol.dataReceived("CONNECT 127.0.0.1:wrong_port HTTP/1.0" +
437 CRLF * 2)
438
439 status_line = protocol.transport.getvalue()
440 self.assertTrue(status_line.startswith("HTTP/1.0 400 "),
441 "The port must be an integer.")
442
443 @defer.inlineCallbacks
444 def test_connection_is_finished_when_stopping(self):
445 """The client disconnects when requested."""
446 fake_protocol = FakeServerTunnelProtocol()
447 client = tunnel_server.RemoteSocket(fake_protocol)
448 url = urlparse(self.dest_url)
449 yield client.connect(url.netloc)
450 yield client.stop()
451
452 @defer.inlineCallbacks
453 def test_stop_but_never_connected(self):
454 """Stop but it was never connected."""
455 fake_protocol = FakeServerTunnelProtocol()
456 client = tunnel_server.RemoteSocket(fake_protocol)
457 yield client.stop()
458
459 @defer.inlineCallbacks
460 def test_client_write(self):
461 """Data written to the client is sent to the other side."""
462 fake_protocol = FakeServerTunnelProtocol()
463 client = tunnel_server.RemoteSocket(fake_protocol)
464 self.addCleanup(client.stop)
465 url = urlparse(self.dest_url)
466 yield client.connect(url.netloc)
467 client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
468 yield self.ws.simple_resource.rendered
469
470 @defer.inlineCallbacks
471 def test_client_read(self):
472 """Data received by the client is written into the transport."""
473 fake_protocol = FakeServerTunnelProtocol()
474 client = tunnel_server.RemoteSocket(fake_protocol)
475 self.addCleanup(client.stop)
476 url = urlparse(self.dest_url)
477 yield client.connect(url.netloc)
478 client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
479 yield self.ws.simple_resource.rendered
480 data = yield fake_protocol.response_received
481 _headers, content = str(data).split(CRLF * 2, 1)
482 self.assertEqual(content, SAMPLE_CONTENT)
483
484
485class AnonProxyRemoteSocketTestCase(RemoteSocketTestCase):
486 """Tests for the client going thru an anonymous proxy."""
487
488 get_proxy_settings = RemoteSocketTestCase.get_nonauth_proxy_settings
489
490 def parse_headers(self, raw_headers):
491 """Parse the headers."""
492 lines = raw_headers.split(CRLF)
493 header_lines = lines[1:]
494 headers_pairs = (l.split(":", 1) for l in header_lines)
495 return dict((k.lower(), v.strip()) for k, v in headers_pairs)
496
497 @defer.inlineCallbacks
498 def test_verify_client_uses_proxy(self):
499 """Verify that the client uses the proxy."""
500 fake_protocol = FakeServerTunnelProtocol()
501 client = tunnel_server.RemoteSocket(fake_protocol)
502 self.addCleanup(client.stop)
503 url = urlparse(self.dest_url)
504 yield client.connect(url.netloc)
505 client.write("GET /simpleresource HTTP/1.0" + CRLF * 2)
506 yield self.ws.simple_resource.rendered
507 data = yield fake_protocol.response_received
508 raw_headers, _content = str(data).split(CRLF * 2, 1)
509 self.parse_headers(raw_headers)
510
511
512@skipIfOS('linux2', 'LP: #1111880 - ncsa_auth crashing for auth proxy tests.')
513class AuthenticatedProxyRemoteSocketTestCase(AnonProxyRemoteSocketTestCase):
514 """Tests for the client going thru an authenticated proxy."""
515
516 get_proxy_settings = RemoteSocketTestCase.get_auth_proxy_settings
517
518 @defer.inlineCallbacks
519 def test_proxy_authentication_error(self):
520 """The proxy credentials were wrong on purpose."""
521 settings = {"http": self.get_proxy_settings()}
522 settings["http"]["password"] = "wrong password!!!"
523 proxy = tunnel_server.build_proxy(settings)
524 tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
525 fake_protocol = FakeServerTunnelProtocol()
526 client = tunnel_server.RemoteSocket(fake_protocol)
527 self.addCleanup(client.stop)
528 url = urlparse(self.dest_url)
529 yield self.assertFailure(client.connect(url.netloc),
530 tunnel_server.ProxyAuthenticationError)
531
532 @defer.inlineCallbacks
533 def test_proxy_nobody_listens(self):
534 """The proxy settings point to a proxy that's unreachable."""
535 settings = dict(http={
536 "host": "127.0.0.1",
537 "port": 83, # unused port according to /etc/services
538 })
539 proxy = tunnel_server.build_proxy(settings)
540 tunnel_server.QNetworkProxy.setApplicationProxy(proxy)
541 fake_protocol = FakeServerTunnelProtocol()
542 client = tunnel_server.RemoteSocket(fake_protocol)
543 self.addCleanup(client.stop)
544 url = urlparse(self.dest_url)
545 yield self.assertFailure(client.connect(url.netloc),
546 tunnel_server.ConnectionError)
547
548 def test_use_credentials(self):
549 """The credentials are used if present."""
550 fake_protocol = FakeServerTunnelProtocol()
551 client = tunnel_server.RemoteSocket(fake_protocol)
552 proxy = tunnel_server.build_proxy(FAKE_SETTINGS)
553 authenticator = QAuthenticator()
554
555 client.proxyAuthenticationRequired.emit(proxy, authenticator)
556 self.assertEqual(proxy.user(), "")
557 self.assertEqual(proxy.password(), "")
558 fake_protocol.proxy_credentials = FAKE_CREDS
559
560 client.proxyAuthenticationRequired.emit(proxy, authenticator)
561 self.assertEqual(authenticator.user(), FAKE_CREDS["username"])
562 self.assertEqual(authenticator.password(), FAKE_CREDS["password"])
563
564
565class FakeNetworkProxyFactoryClass(object):
566 """A fake QNetworkProxyFactory."""
567 last_query = None
568 use_system = False
569
570 def __init__(self, enabled):
571 """Initialize this fake instance."""
572 if enabled:
573 self.proxy_type = tunnel_server.QNetworkProxy.HttpProxy
574 else:
575 self.proxy_type = tunnel_server.QNetworkProxy.NoProxy
576
577 def type(self):
578 """Return the proxy type configured."""
579 return self.proxy_type
580
581 @classmethod
582 def setUseSystemConfiguration(cls, new_value):
583 """Save the system configuration requested."""
584 cls.use_system = new_value
585
586 @classmethod
587 def useSystemConfiguration(cls):
588 """Is the system configured for proxies?"""
589 return cls.use_system
590
591 def systemProxyForQuery(self, query):
592 """A list of proxies, but only type() will be called on the first."""
593 return [self]
594
595
596class CheckProxyEnabledTestCase(TestCase):
597 """Tests for the check_proxy_enabled function."""
598
599 @defer.inlineCallbacks
600 def setUp(self):
601 """Initialize this testcase."""
602 yield super(CheckProxyEnabledTestCase, self).setUp()
603 self.app_proxy = []
604
605 def _assert_proxy_state(self, platform, state, assertion):
606 """Assert the proxy is in a given state."""
607 self.patch(tunnel_server.QNetworkProxy, "setApplicationProxy",
608 lambda proxy: self.app_proxy.append(proxy))
609 self.patch(tunnel_server.sys, "platform", platform)
610 ret = tunnel_server.check_proxy_enabled(SAMPLE_HOST, str(SAMPLE_PORT))
611 self.assertTrue(ret == state, assertion)
612
613 def _assert_proxy_enabled(self, platform):
614 """Assert that the proxy is enabled."""
615 self._assert_proxy_state(platform, True, "Proxy is enabled.")
616
617 def _assert_proxy_disabled(self, platform):
618 """Assert that the proxy is disabled."""
619 self._assert_proxy_state(platform, False, "Proxy is disabled.")
620
621 def test_platform_linux_enabled(self):
622 """Tests for the linux platform with proxies enabled."""
623 self.patch(tunnel_server.gsettings, "get_proxy_settings",
624 lambda: FAKE_SETTINGS)
625 self._assert_proxy_enabled("linux3")
626 self.assertEqual(len(self.app_proxy), 1)
627
628 def test_platform_linux_disabled(self):
629 """Tests for the linux platform with proxies disabled."""
630 self.patch(tunnel_server.gsettings, "get_proxy_settings", lambda: {})
631 self._assert_proxy_disabled("linux3")
632 self.assertEqual(len(self.app_proxy), 0)
633
634 def test_platform_other_enabled(self):
635 """Tests for any other platform with proxies enabled."""
636 fake_netproxfact = FakeNetworkProxyFactoryClass(True)
637 self.patch(tunnel_server, "QNetworkProxyFactory", fake_netproxfact)
638 self._assert_proxy_enabled("windows 1.0")
639 self.assertEqual(len(self.app_proxy), 0)
640 self.assertTrue(fake_netproxfact.useSystemConfiguration())
641
642 def test_platform_other_disabled(self):
643 """Tests for any other platform with proxies disabled."""
644 fake_netproxfact = FakeNetworkProxyFactoryClass(False)
645 self.patch(tunnel_server, "QNetworkProxyFactory", fake_netproxfact)
646 self._assert_proxy_disabled("windows 1.0")
647 self.assertEqual(len(self.app_proxy), 0)
648 self.assertTrue(fake_netproxfact.useSystemConfiguration())
649
650
651class FakeQCoreApp(object):
652 """A fake QCoreApplication."""
653
654 fake_instance = None
655
656 def __init__(self, argv):
657 """Initialize this fake."""
658 self.executed = False
659 self.argv = argv
660 FakeQCoreApp.fake_instance = self
661
662 def exec_(self):
663 """Fake the execution of this app."""
664 self.executed = True
665
666 @staticmethod
667 def instance():
668 """But return the real instance."""
669 return QCoreApplication.instance()
670
671
672class MainFunctionTestCase(TestCase):
673 """Tests for the main function of the tunnel server."""
674
675 @defer.inlineCallbacks
676 def setUp(self):
677 """Initialize these testcases."""
678 yield super(MainFunctionTestCase, self).setUp()
679 self.called = []
680 self.proxies_enabled = False
681
682 def fake_is_proxy_enabled(*args):
683 """Store the call, return false."""
684 self.called.append(args)
685 return self.proxies_enabled
686
687 self.patch(tunnel_server, "check_proxy_enabled", fake_is_proxy_enabled)
688 self.fake_stdout = StringIO()
689 self.patch(tunnel_server.sys, "stdout", self.fake_stdout)
690 self.patch(tunnel_server, "QCoreApplication", FakeQCoreApp)
691
692 def test_checks_proxies(self):
693 """Main checks that the proxies are enabled."""
694 tunnel_server.main([])
695 self.assertEqual(len(self.called), 1)
696
697 def test_on_proxies_enabled_prints_port_and_cookie(self):
698 """With proxies enabled print port to stdout and start the mainloop."""
699 self.patch(tunnel_server.uuid, "uuid4", lambda: FAKE_COOKIE)
700 self.proxies_enabled = True
701 port = 443
702 tunnel_server.main(["example.com", str(port)])
703 stdout = self.fake_stdout.getvalue()
704
705 self.assertIn(tunnel_server.TUNNEL_PORT_LABEL + ": ", stdout)
706 cookie_line = tunnel_server.TUNNEL_COOKIE_LABEL + ": " + FAKE_COOKIE
707 self.assertIn(cookie_line, stdout)
708
709 def test_on_proxies_disabled_exit(self):
710 """With proxies disabled, print a message and exit gracefully."""
711 self.proxies_enabled = False
712 tunnel_server.main(["example.com", "443"])
713 self.assertIn("Proxy not enabled.", self.fake_stdout.getvalue())
714 self.assertEqual(FakeQCoreApp.fake_instance, None)
715
716 def test_qtdbus_installed_on_linux(self):
717 """The QtDbus mainloop is installed."""
718 self.patch(tunnel_server.sys, "platform", "linux123")
719 installed = []
720 self.patch(
721 tunnel_server, "install_qt_dbus", lambda: installed.append(None))
722 self.proxies_enabled = True
723 tunnel_server.main(["example.com", "443"])
724 self.assertEqual(len(installed), 1)
725
726 def test_qtdbus_not_installed_on_windows(self):
727 """The QtDbus mainloop is installed."""
728 self.patch(tunnel_server.sys, "platform", "win98")
729 installed = []
730 self.patch(
731 tunnel_server, "install_qt_dbus", lambda: installed.append(None))
732 self.proxies_enabled = True
733 tunnel_server.main(["example.com", "443"])
734 self.assertEqual(len(installed), 0)
735
736 def test_fix_turkish_locale_called(self):
737 """The fix_turkish_locale function is called, always."""
738 called = []
739 self.patch(
740 tunnel_server, "fix_turkish_locale",
741 lambda *args, **kwargs: called.append((args, kwargs)))
742 tunnel_server.main(["localhost", "443"])
743 self.assertEqual(called, [((), {})])
7440
=== removed file 'ubuntuone/proxy/tunnel_client.py'
--- ubuntuone/proxy/tunnel_client.py 2016-05-29 00:50:05 +0000
+++ ubuntuone/proxy/tunnel_client.py 1970-01-01 00:00:00 +0000
@@ -1,202 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Client for the tunnel protocol."""
30
31import logging
32
33from twisted.internet import protocol, reactor
34
35from ubuntuone.clientdefs import NAME
36from ubuntuone.proxy.common import (
37 BaseTunnelProtocol,
38 CRLF,
39 TUNNEL_COOKIE_LABEL,
40 TUNNEL_COOKIE_HEADER,
41 TUNNEL_PORT_LABEL,
42)
43
44METHOD_LINE = "CONNECT %s:%d HTTP/1.0" + CRLF
45LOCALHOST = "127.0.0.1"
46
47logger = logging.getLogger("ubuntuone.SyncDaemon.TunnelClient")
48
49
50class TunnelClientProtocol(BaseTunnelProtocol):
51 """Client protocol for the handshake part of the tunnel."""
52
53 def connectionMade(self):
54 """The connection to the tunnel was made so send request."""
55 method_line = METHOD_LINE % (self.factory.tunnel_host,
56 self.factory.tunnel_port)
57 headers = {
58 "User-Agent": "%s tunnel client" % NAME,
59 TUNNEL_COOKIE_HEADER: self.factory.cookie,
60 }
61 self.transport.write(method_line +
62 self.format_headers(headers) +
63 CRLF)
64
65 def handle_first_line(self, line):
66 """The first line received is the status line."""
67 try:
68 proto_version, self.status_code, description = line.split(" ", 2)
69 except ValueError:
70 self.transport.loseConnection()
71
72 def headers_done(self):
73 """All the headers have arrived. Time to switch protocols."""
74 remaining_data = self.clearLineBuffer()
75 if self.status_code != "200":
76 self.transport.loseConnection()
77 return
78 addr = self.transport.getPeer()
79 other_protocol = self.factory.other_factory.buildProtocol(addr)
80 self.transport.protocol = other_protocol
81 other_protocol.transport = self.transport
82 self.transport = None
83 if self.factory.context_factory:
84 other_protocol.transport.startTLS(self.factory.context_factory)
85 other_protocol.connectionMade()
86 if remaining_data:
87 other_protocol.dataReceived(remaining_data)
88
89
90class TunnelClientFactory(protocol.ClientFactory):
91 """A factory for Tunnel Client Protocols."""
92
93 protocol = TunnelClientProtocol
94
95 def __init__(self, tunnel_host, tunnel_port, other_factory, cookie,
96 context_factory=None):
97 """Initialize this factory."""
98 self.tunnel_host = tunnel_host
99 self.tunnel_port = tunnel_port
100 self.other_factory = other_factory
101 self.context_factory = context_factory
102 self.cookie = cookie
103
104 def startedConnecting(self, connector):
105 """Forward this call to the other factory."""
106 self.other_factory.startedConnecting(connector)
107
108 def clientConnectionFailed(self, connector, reason):
109 """Forward this call to the other factory."""
110 self.other_factory.clientConnectionFailed(connector, reason)
111
112 def clientConnectionLost(self, connector, reason):
113 """Forward this call to the other factory."""
114 self.other_factory.clientConnectionLost(connector, reason)
115
116
117class TunnelClient(object):
118 """A client for the proxy tunnel."""
119
120 def __init__(self, tunnel_host, tunnel_port, cookie):
121 """Initialize this client."""
122 self.tunnel_host = tunnel_host
123 self.tunnel_port = tunnel_port
124 self.cookie = cookie
125
126 def connectTCP(self, host, port, factory, *args, **kwargs):
127 """A connectTCP going thru the tunnel."""
128 logger.info("Connecting (TCP) to %r:%r via tunnel at %r:%r",
129 host, port, self.tunnel_host, self.tunnel_port)
130 tunnel_factory = TunnelClientFactory(host, port, factory, self.cookie)
131 return reactor.connectTCP(self.tunnel_host, self.tunnel_port,
132 tunnel_factory, *args, **kwargs)
133
134 def connectSSL(self, host, port, factory,
135 contextFactory, *args, **kwargs):
136 """A connectSSL going thru the tunnel."""
137 logger.info("Connecting (SSL) to %r:%r via tunnel at %r:%r",
138 host, port, self.tunnel_host, self.tunnel_port)
139 tunnel_factory = TunnelClientFactory(
140 host, port, factory, self.cookie, contextFactory)
141 return reactor.connectTCP(self.tunnel_host, self.tunnel_port,
142 tunnel_factory, *args, **kwargs)
143
144
145class TunnelProcessProtocol(protocol.ProcessProtocol):
146 """The dialog thru stdout with the tunnel server."""
147
148 timeout = 30
149
150 def __init__(self, client_d):
151 """Initialize this protocol."""
152 self.client_d = client_d
153 self.timer = None
154 self.port = None
155 self.cookie = None
156
157 def connectionMade(self):
158 """The process has started, start a timer."""
159 logger.info("Tunnel process started.")
160 self.timer = reactor.callLater(self.timeout, self.process_timeouted)
161
162 def process_timeouted(self):
163 """The process took too long to reply."""
164 if not self.client_d.called:
165 logger.info("Timeout while waiting for tunnel process.")
166 self.client_d.callback(reactor)
167
168 def finish_timeout(self):
169 """Stop the timer from firing."""
170 if self.timer and self.timer.active():
171 logger.debug("canceling timer before connection timeout")
172 self.timer.cancel()
173
174 def processExited(self, status):
175 """The tunnel process has exited with some error code."""
176 self.finish_timeout()
177 logger.info("Tunnel process exit status %r.", status)
178 if not self.client_d.called:
179 logger.debug("Tunnel process exited before TunnelClient created. "
180 "Falling back to reactor")
181 self.client_d.callback(reactor)
182
183 def outReceived(self, data):
184 """Receive the port number."""
185 if self.client_d.called:
186 return
187
188 for line in data.split("\n"):
189 if line.startswith(TUNNEL_PORT_LABEL):
190 _header, port = line.split(":", 1)
191 self.port = int(port.strip())
192 if line.startswith(TUNNEL_COOKIE_LABEL):
193 _header, cookie = line.split(":", 1)
194 self.cookie = cookie.strip()
195
196 if self.port and self.cookie:
197 logger.info("Tunnel process listening on port %r.", self.port)
198 client = TunnelClient(LOCALHOST, self.port, self.cookie)
199 self.client_d.callback(client)
200
201 def errReceived(self, data):
202 logger.debug("Got stderr from tunnel process: %r", data)
2030
=== removed file 'ubuntuone/proxy/tunnel_server.py'
--- ubuntuone/proxy/tunnel_server.py 2017-01-07 18:51:07 +0000
+++ ubuntuone/proxy/tunnel_server.py 1970-01-01 00:00:00 +0000
@@ -1,405 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012-2013 Canonical Ltd.
4# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18# In addition, as a special exception, the copyright holders give
19# permission to link the code of portions of this program with the
20# OpenSSL library under certain conditions as described in each
21# individual source file, and distribute linked combinations
22# including the two.
23# You must obey the GNU General Public License in all respects
24# for all of the code used other than OpenSSL. If you modify
25# file(s) with this exception, you may extend this exception to your
26# version of the file(s), but you are not obligated to do so. If you
27# do not wish to do so, delete this exception statement from your
28# version. If you delete this exception statement from all source
29# files in the program, then also delete it here.
30"""A tunnel through proxies.
31
32The layers in a tunneled proxied connection:
33
34↓ tunnelclient - initiates tcp to tunnelserver, request outward connection
35↕ client protocol - started after the tunneclient gets connected
36---process boundary---
37↕ tunnelserver - creates a tunnel instance per incoming connection
38↕ tunnel - hold a qtcpsocket to tunnelclient, and srvtunnelproto to the remote
39↕ servertunnelprotocol - gets CONNECT from tunnelclient, creates a remotesocket
40↕ remotesocket - connects to the destination server via a proxy
41↕ proxy server - goes thru firewalls
42↑ server - dialogues with the client protocol
43
44"""
45
46import sys
47import uuid
48
49from PyQt4.QtCore import QCoreApplication, QTimer
50from PyQt4.QtNetwork import (
51 QAbstractSocket,
52 QHostAddress,
53 QNetworkProxy,
54 QNetworkProxyQuery,
55 QNetworkProxyFactory,
56 QTcpServer,
57 QTcpSocket,
58)
59from twisted.internet import defer, interfaces
60from zope.interface import implements
61
62from ubuntuone.clientdefs import NAME
63from ubuntuone.keyring import Keyring
64from ubuntuone.utils import gsettings
65from ubuntuone.proxy.common import (
66 BaseTunnelProtocol,
67 CRLF,
68 TUNNEL_COOKIE_HEADER,
69 TUNNEL_COOKIE_LABEL,
70 TUNNEL_PORT_LABEL,
71)
72from ubuntuone.proxy.logger import logger
73try:
74 from ubuntuone.utils.locale import fix_turkish_locale
75except ImportError:
76 def fix_turkish_locale():
77 return None
78
79
80DEFAULT_CODE = 500
81DEFAULT_DESCRIPTION = "Connection error"
82
83
84class ConnectionError(Exception):
85 """The client failed connecting to the destination."""
86
87 def __init__(self, code=DEFAULT_CODE, description=DEFAULT_DESCRIPTION):
88 self.code = code
89 self.description = description
90
91
92class ProxyAuthenticationError(ConnectionError):
93 """Credentials mismatch going thru a proxy."""
94
95
96def build_proxy(settings_groups):
97 """Create a QNetworkProxy from these settings."""
98 proxy_groups = [
99 ("socks", QNetworkProxy.Socks5Proxy),
100 ("https", QNetworkProxy.HttpProxy),
101 ("http", QNetworkProxy.HttpProxy),
102 ]
103 for group, proxy_type in proxy_groups:
104 if group not in settings_groups:
105 continue
106 settings = settings_groups[group]
107 if "host" in settings and "port" in settings:
108 return QNetworkProxy(proxy_type,
109 hostName=settings.get("host", ""),
110 port=settings.get("port", 0),
111 user=settings.get("username", ""),
112 password=settings.get("password", ""))
113 logger.error("No proxy correctly configured.")
114 return QNetworkProxy(QNetworkProxy.DefaultProxy)
115
116
117class RemoteSocket(QTcpSocket):
118 """A dumb connection through a proxy to a destination hostport."""
119
120 def __init__(self, tunnel_protocol):
121 """Initialize this object."""
122 super(RemoteSocket, self).__init__()
123 self.protocol = tunnel_protocol
124 self.connected_d = defer.Deferred()
125 self.connected.connect(self.handle_connected)
126 self.proxyAuthenticationRequired.connect(self.handle_auth_required)
127 self.buffered_data = []
128
129 def handle_connected(self):
130 """When connected, send all pending data."""
131 self.disconnected.connect(self.handle_disconnected)
132 self.connected_d.callback(None)
133 for d in self.buffered_data:
134 logger.debug("writing remote: %d bytes", len(d))
135 super(RemoteSocket, self).write(d)
136 self.buffered_data = []
137
138 def handle_disconnected(self):
139 """Do something with disconnections."""
140 logger.debug("Remote socket disconnected")
141 self.protocol.remote_disconnected()
142
143 def write(self, data):
144 """Write data to the remote end, buffering if not connected."""
145 if self.state() == QAbstractSocket.ConnectedState:
146 logger.debug("writing remote: %d bytes", len(data))
147 super(RemoteSocket, self).write(data)
148 else:
149 self.buffered_data.append(data)
150
151 def connect(self, hostport):
152 """Try to establish the connection to the remote end."""
153 host, port = hostport.split(":")
154
155 try:
156 port = int(port)
157 except ValueError:
158 raise ConnectionError(400, "Destination port must be an integer.")
159
160 self.readyRead.connect(self.handle_ready_read)
161 self.error.connect(self.handle_error)
162 self.connectToHost(host, port)
163
164 return self.connected_d
165
166 def handle_auth_required(self, proxy, authenticator):
167 """Handle the proxyAuthenticationRequired signal."""
168 self.protocol.proxy_auth_required(proxy, authenticator)
169
170 def handle_error(self, socket_error):
171 """Some error happened while connecting."""
172 error_description = "%s (%d)" % (self.errorString(), socket_error)
173 logger.error("connection error: %s", error_description)
174 if self.connected_d.called:
175 return
176
177 if socket_error == self.ProxyAuthenticationRequiredError:
178 error = ProxyAuthenticationError(407, error_description)
179 else:
180 error = ConnectionError(500, error_description)
181
182 self.connected_d.errback(error)
183
184 def handle_ready_read(self):
185 """Forward data from the remote end to the parent protocol."""
186 data = self.readAll()
187 self.protocol.response_data_received(data)
188
189 @defer.inlineCallbacks
190 def stop(self):
191 """Finish and cleanup."""
192 self.disconnectFromHost()
193 while self.state() != self.UnconnectedState:
194 d = defer.Deferred()
195 QTimer.singleShot(100, lambda: d.callback(None))
196 yield d
197
198
199class ServerTunnelProtocol(BaseTunnelProtocol):
200 """CONNECT sever protocol for tunnelling connections."""
201
202 def __init__(self, client_class):
203 """Initialize this protocol."""
204 BaseTunnelProtocol.__init__(self)
205 self.hostport = ""
206 self.client = None
207 self.client_class = client_class
208 self.proxy_credentials = None
209 self.proxy_domain = None
210
211 def error_response(self, code, description):
212 """Write a response with an error, and disconnect."""
213 self.write_transport("HTTP/1.0 %d %s" % (code, description) + CRLF * 2)
214 self.transport.loseConnection()
215 if self.client:
216 self.client.stop()
217 self.clearLineBuffer()
218
219 def write_transport(self, data):
220 """Write a response in the transport."""
221 self.transport.write(data)
222
223 def proxy_auth_required(self, proxy, authenticator):
224 """Proxy authentication is required."""
225 logger.info("auth_required %r, %r",
226 proxy.hostName(), self.proxy_domain)
227 if self.proxy_credentials and proxy.hostName() == self.proxy_domain:
228 logger.info("Credentials added to authenticator.")
229 authenticator.setUser(self.proxy_credentials["username"])
230 authenticator.setPassword(self.proxy_credentials["password"])
231 else:
232 logger.info("Credentials needed, but none available.")
233 self.proxy_domain = proxy.hostName()
234
235 def handle_first_line(self, line):
236 """Special handling for the first line received."""
237 try:
238 method, hostport, proto_version = line.split(" ", 2)
239 if proto_version != "HTTP/1.0":
240 self.error_response(505, "HTTP Version Not Supported")
241 return
242 if method != "CONNECT":
243 self.error_response(405, "Only the CONNECT method is allowed")
244 return
245 self.hostport = hostport
246 except ValueError:
247 self.error_response(400, "Bad request")
248
249 def verify_cookie(self):
250 """Fail if the cookie is wrong or missing."""
251 cookie_received = dict(self.received_headers).get(TUNNEL_COOKIE_HEADER)
252 if cookie_received != self.transport.cookie:
253 raise ConnectionError(418, "Please see RFC 2324")
254
255 @defer.inlineCallbacks
256 def headers_done(self):
257 """An empty line was received, start connecting and switch mode."""
258 try:
259 self.verify_cookie()
260 try:
261 logger.info("Connecting once")
262 self.client = self.client_class(self)
263 yield self.client.connect(self.hostport)
264 except ProxyAuthenticationError:
265 if not self.proxy_domain:
266 logger.info("No proxy domain defined")
267 raise
268
269 credentials = yield Keyring().get_credentials(
270 str(self.proxy_domain))
271 if "username" in credentials:
272 self.proxy_credentials = credentials
273 logger.info("Connecting again with keyring credentials")
274 self.client = self.client_class(self)
275 yield self.client.connect(self.hostport)
276 logger.info("Connected with keyring credentials")
277
278 response_headers = {
279 "Server": "%s proxy tunnel" % NAME,
280 }
281 self.write_transport("HTTP/1.0 200 Proxy connection established" +
282 CRLF + self.format_headers(response_headers) +
283 CRLF)
284 except ConnectionError as e:
285 logger.exception("Connection error")
286 self.error_response(e.code, e.description)
287 except Exception:
288 logger.exception("Unhandled problem while connecting")
289
290 def rawDataReceived(self, data):
291 """Tunnel all raw data straight to the other side."""
292 self.client.write(data)
293
294 def response_data_received(self, data):
295 """Return data coming from the other side."""
296 self.write_transport(data)
297
298
299class Tunnel(object):
300 """An instance of a running tunnel."""
301
302 implements(interfaces.ITransport)
303
304 def __init__(self, local_socket, cookie):
305 """Initialize this Tunnel instance."""
306 self.cookie = cookie
307 self.disconnecting = False
308 self.local_socket = local_socket
309 self.protocol = ServerTunnelProtocol(RemoteSocket)
310 self.protocol.transport = self
311 local_socket.readyRead.connect(self.server_ready_read)
312 local_socket.disconnected.connect(self.local_disconnected)
313
314 def server_ready_read(self):
315 """Data available on the local end. Move it forward."""
316 data = bytes(self.local_socket.readAll())
317 self.protocol.dataReceived(data)
318
319 def write(self, data):
320 """Data available on the remote end. Bring it back."""
321 logger.debug("writing local: %d bytes", len(data))
322 self.local_socket.write(data)
323
324 def loseConnection(self):
325 """The remote end disconnected."""
326 logger.debug("disconnecting local end.")
327 self.local_socket.close()
328
329 def local_disconnected(self):
330 """The local end disconnected."""
331 logger.debug("The local socket got disconnected.")
332 # TODO: handle this case in an upcoming branch
333
334
335class TunnelServer(object):
336 """A server for tunnel instances."""
337
338 def __init__(self, cookie):
339 """Initialize this tunnel instance."""
340 self.tunnels = []
341 self.cookie = cookie
342 self.server = QTcpServer(QCoreApplication.instance())
343 self.server.newConnection.connect(self.new_connection)
344 self.server.listen(QHostAddress.LocalHost, 0)
345 logger.info("Starting tunnel server at port %d", self.port)
346
347 def new_connection(self):
348 """On a new connection create a new tunnel instance."""
349 logger.info("New connection made")
350 local_socket = self.server.nextPendingConnection()
351 tunnel = Tunnel(local_socket, self.cookie)
352 self.tunnels.append(tunnel)
353
354 def shutdown(self):
355 """Terminate every connection."""
356 # TODO: handle this gracefully in an upcoming branch
357
358 @property
359 def port(self):
360 """The port where this server listens."""
361 return self.server.serverPort()
362
363
364def check_proxy_enabled(host, port):
365 """Check if the proxy is enabled."""
366 port = int(port)
367 if sys.platform.startswith("linux"):
368 settings = gsettings.get_proxy_settings()
369 enabled = len(settings) > 0
370 if enabled:
371 proxy = build_proxy(settings)
372 QNetworkProxy.setApplicationProxy(proxy)
373 else:
374 logger.info("Proxy is disabled.")
375 return enabled
376 else:
377 QNetworkProxyFactory.setUseSystemConfiguration(True)
378 query = QNetworkProxyQuery(host, port)
379 proxies = QNetworkProxyFactory.systemProxyForQuery(query)
380 return len(proxies) and proxies[0].type() != QNetworkProxy.NoProxy
381
382
383def install_qt_dbus():
384 """Import and install the qt+dbus integration."""
385 from dbus.mainloop.qt import DBusQtMainLoop
386 DBusQtMainLoop(set_as_default=True)
387
388
389def main(argv):
390 """The main function for the tunnel server."""
391 fix_turkish_locale()
392 if not check_proxy_enabled(*argv[1:]):
393 sys.stdout.write("Proxy not enabled.")
394 sys.stdout.flush()
395 else:
396 if sys.platform.startswith("linux"):
397 install_qt_dbus()
398
399 app = QCoreApplication(argv)
400 cookie = str(uuid.uuid4())
401 tunnel_server = TunnelServer(cookie)
402 sys.stdout.write("%s: %d\n" % (TUNNEL_PORT_LABEL, tunnel_server.port) +
403 "%s: %s\n" % (TUNNEL_COOKIE_LABEL, cookie))
404 sys.stdout.flush()
405 app.exec_()
4060
=== modified file 'ubuntuone/syncdaemon/action_queue.py'
--- ubuntuone/syncdaemon/action_queue.py 2017-02-10 01:15:07 +0000
+++ ubuntuone/syncdaemon/action_queue.py 2018-03-14 21:14:07 +0000
@@ -1,7 +1,7 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2#2#
3# Copyright 2009-2015 Canonical Ltd.3# Copyright 2009-2015 Canonical Ltd.
4# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)4# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
5#5#
6# This program is free software: you can redistribute it and/or modify it6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published7# under the terms of the GNU General Public License version 3, as published
@@ -59,7 +59,6 @@
59from ubuntuone.syncdaemon.interfaces import IActionQueue, IMarker59from ubuntuone.syncdaemon.interfaces import IActionQueue, IMarker
60from ubuntuone.syncdaemon.logger import mklog, TRACE60from ubuntuone.syncdaemon.logger import mklog, TRACE
61from ubuntuone.syncdaemon import config, offload_queue61from ubuntuone.syncdaemon import config, offload_queue
62from ubuntuone.syncdaemon import tunnel_runner
6362
64logger = logging.getLogger("ubuntuone.SyncDaemon.ActionQueue")63logger = logging.getLogger("ubuntuone.SyncDaemon.ActionQueue")
6564
@@ -856,27 +855,20 @@
856 self.event_queue.push('SV_VOLUME_NEW_GENERATION',855 self.event_queue.push('SV_VOLUME_NEW_GENERATION',
857 volume_id=volume_id, generation=generation)856 volume_id=volume_id, generation=generation)
858857
859 def _get_tunnel_runner(self, host, port):
860 """Build the tunnel runner."""
861 return tunnel_runner.TunnelRunner(host, port)
862
863 @defer.inlineCallbacks
864 def _make_connection(self):858 def _make_connection(self):
865 """Do the real connect call."""859 """Do the real connect call."""
866 connection_info = self.connection_info.next()860 connection_info = self.connection_info.next()
867 logger.info("Attempting connection to %s", connection_info)861 logger.info("Attempting connection to %s", connection_info)
868 host = connection_info['host']862 host = connection_info['host']
869 port = connection_info['port']863 port = connection_info['port']
870 tunnelrunner = self._get_tunnel_runner(host, port)
871 client = yield tunnelrunner.get_client()
872 if connection_info['use_ssl']:864 if connection_info['use_ssl']:
873 ssl_context = get_ssl_context(865 ssl_context = get_ssl_context(
874 connection_info['disable_ssl_verify'], host)866 connection_info['disable_ssl_verify'], host)
875 self.connector = client.connectSSL(867 self.connector = reactor.connectSSL(
876 host, port, factory=self, contextFactory=ssl_context,868 host, port, factory=self, contextFactory=ssl_context,
877 timeout=self.connection_timeout)869 timeout=self.connection_timeout)
878 else:870 else:
879 self.connector = client.connectTCP(871 self.connector = reactor.connectTCP(
880 host, port, self, timeout=self.connection_timeout)872 host, port, self, timeout=self.connection_timeout)
881873
882 def connect(self):874 def connect(self):
883875
=== modified file 'ubuntuone/syncdaemon/tests/test_action_queue.py'
--- ubuntuone/syncdaemon/tests/test_action_queue.py 2018-03-08 19:39:13 +0000
+++ ubuntuone/syncdaemon/tests/test_action_queue.py 2018-03-14 21:14:07 +0000
@@ -1,7 +1,7 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2#2#
3# Copyright 2009-2015 Canonical Ltd.3# Copyright 2009-2015 Canonical Ltd.
4# Copyright 2016-2017 Chicharreros (https://launchpad.net/~chicharreros)4# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
5#5#
6# This program is free software: you can redistribute it and/or modify it6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published7# under the terms of the GNU General Public License version 3, as published
@@ -1384,9 +1384,13 @@
1384 self.assertTrue(self.handler.check_info("Connection started",1384 self.assertTrue(self.handler.check_info("Connection started",
1385 "host 1.2.3.4", "port 4321"))1385 "host 1.2.3.4", "port 4321"))
13861386
1387 @defer.inlineCallbacks
1388 def test_connection_info_rotation(self):1387 def test_connection_info_rotation(self):
1389 """It tries to connect to different servers."""1388 """It tries to connect to different servers."""
1389 # store how connectTCP is called
1390 called = []
1391 self.patch(
1392 reactor, 'connectTCP',
1393 lambda host, port, *a, **k: called.append((host, port)))
13901394
1391 multiple_conn = [1395 multiple_conn = [
1392 {'host': 'host1', 'port': 'port1', 'use_ssl': False},1396 {'host': 'host1', 'port': 'port1', 'use_ssl': False},
@@ -1394,61 +1398,14 @@
1394 ]1398 ]
1395 self.action_queue.connection_info = itertools.cycle(multiple_conn)1399 self.action_queue.connection_info = itertools.cycle(multiple_conn)
13961400
1397 self.tunnel_runner = None1401 self.action_queue._make_connection()
13981402 self.assertEqual(called[-1], ('host1', 'port1'))
1399 def mitm(*args):1403
1400 tunnel_runner = SavingConnectionTunnelRunner(*args)1404 self.action_queue._make_connection()
1401 self.tunnel_runner = tunnel_runner1405 self.assertEqual(called[-1], ('host2', 'port2'))
1402 return tunnel_runner1406
14031407 self.action_queue._make_connection()
1404 self.action_queue._get_tunnel_runner = mitm1408 self.assertEqual(called[-1], ('host1', 'port1'))
1405
1406 yield self.action_queue._make_connection()
1407 self.assertEqual(self.tunnel_runner.host, 'host1')
1408 self.assertEqual(self.tunnel_runner.port, 'port1')
1409
1410 yield self.action_queue._make_connection()
1411 self.assertEqual(self.tunnel_runner.host, 'host2')
1412 self.assertEqual(self.tunnel_runner.port, 'port2')
1413
1414 yield self.action_queue._make_connection()
1415 self.assertEqual(self.tunnel_runner.host, 'host1')
1416 self.assertEqual(self.tunnel_runner.port, 'port1')
1417
1418
1419class TunnelRunnerTestCase(FactoryBaseTestCase):
1420 """Tests for the tunnel runner."""
1421
1422 tunnel_runner_class = SavingConnectionTunnelRunner
1423
1424 def setUp(self):
1425 result = super(TunnelRunnerTestCase, self).setUp()
1426 self.tunnel_runner = None
1427 orig_get_tunnel_runner = self.action_queue._get_tunnel_runner
1428
1429 def mitm(*args):
1430 tunnel_runner = orig_get_tunnel_runner(*args)
1431 self.tunnel_runner = tunnel_runner
1432 return tunnel_runner
1433
1434 self.action_queue._get_tunnel_runner = mitm
1435 return result
1436
1437 @defer.inlineCallbacks
1438 def test_make_connection_uses_tunnelrunner_non_ssl(self):
1439 """Check that _make_connection uses TunnelRunner."""
1440 self._patch_connection_info(use_ssl=False)
1441 yield self.action_queue._make_connection()
1442 self.assertTrue(self.tunnel_runner.client.tcp_connected,
1443 "connectTCP is called on the client.")
1444
1445 @defer.inlineCallbacks
1446 def test_make_connection_uses_tunnelrunner_ssl(self):
1447 """Check that _make_connection uses TunnelRunner."""
1448 self._patch_connection_info(use_ssl=True, disable_ssl_verify=False)
1449 yield self.action_queue._make_connection()
1450 self.assertTrue(self.tunnel_runner.client.ssl_connected,
1451 "connectSSL is called on the client.")
14521409
14531410
1454class ContextRequestedWithHost(FactoryBaseTestCase):1411class ContextRequestedWithHost(FactoryBaseTestCase):
@@ -1459,6 +1416,9 @@
1459 @defer.inlineCallbacks1416 @defer.inlineCallbacks
1460 def test_context_request_passes_host(self):1417 def test_context_request_passes_host(self):
1461 """The context is requested passing the host."""1418 """The context is requested passing the host."""
1419 # avoid a real connection
1420 self.patch(reactor, 'connectSSL', lambda *a, **k: None)
1421
1462 fake_host = "fake_host"1422 fake_host = "fake_host"
1463 fake_disable_ssl_verify = False1423 fake_disable_ssl_verify = False
14641424
14651425
=== removed file 'ubuntuone/syncdaemon/tests/test_tunnel_runner.py'
--- ubuntuone/syncdaemon/tests/test_tunnel_runner.py 2016-06-04 21:14:35 +0000
+++ ubuntuone/syncdaemon/tests/test_tunnel_runner.py 1970-01-01 00:00:00 +0000
@@ -1,185 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Tests for the proxy tunnel runner."""
30
31from twisted.internet import defer, error, reactor, task
32from twisted.trial.unittest import TestCase
33
34from ubuntuone.proxy.tests import FAKE_COOKIE
35from ubuntuone.proxy import tunnel_client
36from ubuntuone.syncdaemon import tunnel_runner
37
38FAKE_HOST = "fs-1.two.ubuntu.com"
39FAKE_PORT = 443
40
41
42class TunnelRunnerConstructorTestCase(TestCase):
43 """Test the tunnel runner constructor."""
44
45 timeout = 3
46
47 def raise_import_error(self, *args):
48 """Raise an import error."""
49 raise ImportError
50
51 @defer.inlineCallbacks
52 def test_proxy_support_not_installed(self):
53 """The proxy support binary package is not installed."""
54 self.patch(tunnel_runner.TunnelRunner, "start_process",
55 self.raise_import_error)
56 tr = tunnel_runner.TunnelRunner(FAKE_HOST, FAKE_PORT)
57 client = yield tr.get_client()
58 self.assertEqual(client, reactor)
59
60 @defer.inlineCallbacks
61 def test_executable_not_found(self):
62 """The executable is not found anywhere."""
63 self.patch(tunnel_runner, "get_tunnel_bin_cmd",
64 lambda *args, **kwargs: ["this_does_not_exist"])
65 tr = tunnel_runner.TunnelRunner(FAKE_HOST, FAKE_PORT)
66 client = yield tr.get_client()
67 self.assertEqual(client, reactor)
68
69
70class FakeProcessTransport(object):
71 """A fake ProcessTransport."""
72
73 pid = 0
74
75 def __init__(self):
76 """Initialize this fake."""
77 self._signals_sent = []
78
79 def signalProcess(self, signalID):
80 """Send a signal to the process."""
81 self._signals_sent.append(signalID)
82
83
84class TunnelRunnerTestCase(TestCase):
85 """Tests for the TunnelRunner."""
86
87 timeout = 3
88
89 @defer.inlineCallbacks
90 def setUp(self):
91 """Initialize this testcase."""
92 yield super(TunnelRunnerTestCase, self).setUp()
93 self.spawned = []
94 self.triggers = []
95 self.fake_process_transport = FakeProcessTransport()
96
97 def fake_spawn_process(*args, **kwargs):
98 """A fake spawnProcess."""
99 self.spawned.append((args, kwargs))
100 return self.fake_process_transport
101
102 self.patch(tunnel_client.reactor, "spawnProcess", fake_spawn_process)
103
104 def fake_add_system_event_trigger(*args, **kwargs):
105 """A fake addSystemEventTrigger."""
106 self.triggers.append((args, kwargs))
107
108 self.patch(tunnel_client.reactor, "addSystemEventTrigger",
109 fake_add_system_event_trigger)
110 self.process_protocol = None
111 self.process_protocol_class = tunnel_client.TunnelProcessProtocol
112 self.patch(tunnel_client, "TunnelProcessProtocol",
113 self.storing_process_protocol_factory)
114 self.tr = tunnel_runner.TunnelRunner("fs-1.one.ubuntu.com", 443)
115
116 def storing_process_protocol_factory(self, *args, **kwargs):
117 """Store the process protocol just created."""
118 self.process_protocol = self.process_protocol_class(*args, **kwargs)
119 return self.process_protocol
120
121 def test_tunnel_process_is_started(self):
122 """The tunnel process is started."""
123 self.assertEqual(
124 len(self.spawned), 1, "The tunnel process is started.")
125
126 def test_system_event_finished(self):
127 """An event is added to stop the process with the reactor."""
128 expected = [(("before", "shutdown", self.tr.stop), {})]
129 self.assertEqual(self.triggers, expected)
130
131 def test_stop_process(self):
132 """The process is stopped if still running."""
133 self.tr.process_transport.pid = 1234
134 self.tr.stop()
135 self.assertEqual(self.fake_process_transport._signals_sent, ["KILL"])
136
137 def test_not_stopped_if_already_finished(self):
138 """Do not stop the tunnel process if it's already finished."""
139 self.tr.process_transport.pid = None
140 self.tr.stop()
141 self.assertEqual(self.fake_process_transport._signals_sent, [])
142
143 @defer.inlineCallbacks
144 def test_tunnel_process_get_client_yielded_twice(self):
145 """The get_client method can be yielded twice."""
146 self.process_protocol.processExited(error.ProcessTerminated(1))
147 client = yield self.tr.get_client()
148 client = yield self.tr.get_client()
149 self.assertNotEqual(client, None)
150
151 @defer.inlineCallbacks
152 def test_tunnel_process_exits_with_error(self):
153 """The tunnel process exits with an error."""
154 self.process_protocol.processExited(error.ProcessTerminated(1))
155 client = yield self.tr.get_client()
156 self.assertEqual(client, reactor)
157
158 @defer.inlineCallbacks
159 def test_tunnel_process_exits_gracefully(self):
160 """The tunnel process exits gracefully."""
161 self.process_protocol.processExited(error.ProcessDone(0))
162 client = yield self.tr.get_client()
163 self.assertEqual(client, reactor)
164
165 @defer.inlineCallbacks
166 def test_tunnel_process_prints_random_garbage_and_timeouts(self):
167 """The tunnel process prints garbage and timeouts."""
168 clock = task.Clock()
169 self.patch(tunnel_client, "reactor", clock)
170 self.process_protocol.connectionMade()
171 self.process_protocol.outReceived("Random garbage")
172 clock.advance(self.process_protocol.timeout)
173 client = yield self.tr.get_client()
174 self.assertEqual(client, clock)
175
176 @defer.inlineCallbacks
177 def test_tunnel_process_prints_port_number_and_cookie(self):
178 """The tunnel process prints the port number."""
179 received = "%s: %d\n%s: %s\n" % (
180 tunnel_client.TUNNEL_PORT_LABEL, FAKE_PORT,
181 tunnel_client.TUNNEL_COOKIE_LABEL, FAKE_COOKIE)
182 self.process_protocol.outReceived(received)
183 client = yield self.tr.get_client()
184 self.assertEqual(client.tunnel_port, FAKE_PORT)
185 self.assertEqual(client.cookie, FAKE_COOKIE)
1860
=== removed file 'ubuntuone/syncdaemon/tunnel_runner.py'
--- ubuntuone/syncdaemon/tunnel_runner.py 2012-11-28 08:08:10 +0000
+++ ubuntuone/syncdaemon/tunnel_runner.py 1970-01-01 00:00:00 +0000
@@ -1,86 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2012 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published
7# by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17# In addition, as a special exception, the copyright holders give
18# permission to link the code of portions of this program with the
19# OpenSSL library under certain conditions as described in each
20# individual source file, and distribute linked combinations
21# including the two.
22# You must obey the GNU General Public License in all respects
23# for all of the code used other than OpenSSL. If you modify
24# file(s) with this exception, you may extend this exception to your
25# version of the file(s), but you are not obligated to do so. If you
26# do not wish to do so, delete this exception statement from your
27# version. If you delete this exception statement from all source
28# files in the program, then also delete it here.
29"""Run the tunnel process and start a client, with a reactor as a fallback."""
30
31import logging
32
33from twisted.internet import defer, reactor
34
35from ubuntuone.clientdefs import LIBEXECDIR
36from ubuntuone.syncdaemon.utils import get_tunnel_bin_cmd
37
38logger = logging.getLogger("ubuntuone.SyncDaemon.TunnelRunner")
39
40
41class TunnelRunner(object):
42 """Run a tunnel process."""
43
44 def __init__(self, host, port):
45 """Start this runner instance."""
46 self.client_d = defer.Deferred()
47 self.process_transport = None
48 try:
49 self.start_process(host, port)
50 except ImportError:
51 logger.info("Proxy support not installed.")
52 self.client_d.callback(reactor)
53 except Exception:
54 logger.exception("Error while starting tunnel process:")
55 self.client_d.callback(reactor)
56
57 def start_process(self, host, port):
58 """Start the tunnel process."""
59 from ubuntuone.proxy.tunnel_client import TunnelProcessProtocol
60 protocol = TunnelProcessProtocol(self.client_d)
61 tunnel_cmd = get_tunnel_bin_cmd(extra_fallbacks=[LIBEXECDIR])
62
63 args = tunnel_cmd + [host, str(port)]
64
65 self.process_transport = reactor.spawnProcess(protocol, args[0],
66 env=None, args=args)
67 reactor.addSystemEventTrigger("before", "shutdown", self.stop)
68
69 def stop(self):
70 """Stop the tunnel process if still running."""
71 logger.info("Stopping process %r", self.process_transport.pid)
72 if self.process_transport.pid is not None:
73 self.process_transport.signalProcess("KILL")
74
75 def get_client(self):
76 """A deferred with the reactor or a tunnel client."""
77
78 def client_selected(result, d):
79 """The tunnel_client or the reactor were selected."""
80 d.callback(result)
81 # make sure the result is available for next callback
82 return result
83
84 d = defer.Deferred()
85 self.client_d.addCallback(client_selected, d)
86 return d
870
=== modified file 'ubuntuone/syncdaemon/utils.py'
--- ubuntuone/syncdaemon/utils.py 2015-09-20 00:03:47 +0000
+++ ubuntuone/syncdaemon/utils.py 2018-03-14 21:14:07 +0000
@@ -1,6 +1,7 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2#2#
3# Copyright 2012 Canonical Ltd.3# Copyright 2012 Canonical Ltd.
4# Copyright 2015-2018 Chicharreros (https://launchpad.net/~chicharreros)
4#5#
5# This program is free software: you can redistribute it and/or modify it6# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU General Public License version 3, as published7# under the terms of the GNU General Public License version 3, as published
@@ -42,10 +43,8 @@
4243
4344
44SYNCDAEMON_EXECUTABLE = 'ubuntuone-syncdaemon'45SYNCDAEMON_EXECUTABLE = 'ubuntuone-syncdaemon'
45TUNNEL_EXECUTABLE = 'ubuntuone-proxy-tunnel'
4646
47DARWIN_APP_NAMES = {SYNCDAEMON_EXECUTABLE: 'UbuntuOne Syncdaemon.app',47DARWIN_APP_NAMES = {SYNCDAEMON_EXECUTABLE: 'UbuntuOne Syncdaemon.app'}
48 TUNNEL_EXECUTABLE: 'UbuntuOne Proxy Tunnel.app'}
4948
5049
51def _get_bin_cmd(exe_name, extra_fallbacks=[]):50def _get_bin_cmd(exe_name, extra_fallbacks=[]):
@@ -73,9 +72,3 @@
73def get_sd_bin_cmd():72def get_sd_bin_cmd():
74 """Get cmd + args to launch syncdaemon executable."""73 """Get cmd + args to launch syncdaemon executable."""
75 return _get_bin_cmd(SYNCDAEMON_EXECUTABLE)74 return _get_bin_cmd(SYNCDAEMON_EXECUTABLE)
76
77
78def get_tunnel_bin_cmd(extra_fallbacks):
79 """Get cmd + args to launch proxy tunnel."""
80 return _get_bin_cmd(TUNNEL_EXECUTABLE,
81 extra_fallbacks=extra_fallbacks)
8275
=== removed file 'ubuntuone/utils/gsettings.py'
--- ubuntuone/utils/gsettings.py 2017-01-07 18:51:07 +0000
+++ ubuntuone/utils/gsettings.py 1970-01-01 00:00:00 +0000
@@ -1,114 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011-2012 Canonical Ltd.
4# Copyright 2015-2016 Chicharreros (https://launchpad.net/~chicharreros)
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18# In addition, as a special exception, the copyright holders give
19# permission to link the code of portions of this program with the
20# OpenSSL library under certain conditions as described in each
21# individual source file, and distribute linked combinations
22# including the two.
23# You must obey the GNU General Public License in all respects
24# for all of the code used other than OpenSSL. If you modify
25# file(s) with this exception, you may extend this exception to your
26# version of the file(s), but you are not obligated to do so. If you
27# do not wish to do so, delete this exception statement from your
28# version. If you delete this exception statement from all source
29# files in the program, then also delete it here.
30
31"""Retrieve the proxy configuration from Gnome."""
32
33import logging
34import subprocess
35
36
37logger = logging.getLogger(__name__)
38GSETTINGS_CMDLINE = "gsettings list-recursively org.gnome.system.proxy"
39CANNOT_PARSE_WARNING = "Cannot parse gsettings value: %r"
40
41
42def parse_proxy_host(hostname):
43 """Parse the host to get username and password."""
44 username = None
45 password = None
46 if "@" in hostname:
47 username, hostname = hostname.rsplit("@", 1)
48 if ":" in username:
49 username, password = username.split(":", 1)
50 return hostname, username, password
51
52
53def parse_manual_proxy_settings(scheme, gsettings):
54 """Parse the settings for a given scheme."""
55 host, username, pwd = parse_proxy_host(gsettings[scheme + ".host"])
56 # if the user did not set a proxy for a type (http/https/ftp) we should
57 # return None to ensure that it is not used
58 if host == '':
59 return None
60
61 settings = {
62 "host": host,
63 "port": gsettings[scheme + ".port"],
64 }
65 if scheme == "http" and gsettings["http.use-authentication"]:
66 username = gsettings["http.authentication-user"]
67 pwd = gsettings["http.authentication-password"]
68 if username is not None and pwd is not None:
69 settings.update({
70 "username": username,
71 "password": pwd,
72 })
73 return settings
74
75
76def get_proxy_settings():
77 """Parse the proxy settings as returned by the gsettings executable."""
78 output = subprocess.check_output(GSETTINGS_CMDLINE.split())
79 gsettings = {}
80 base_len = len("org.gnome.system.proxy.")
81
82 for line in output.split("\n"):
83 try:
84 path, key, value = line.split(" ", 2)
85 except ValueError:
86 continue
87 if value.startswith("'"):
88 parsed_value = value[1:-1]
89 elif value.startswith(('[', '@')):
90 parsed_value = value
91 elif value in ('true', 'false'):
92 parsed_value = (value == 'true')
93 elif value.isdigit():
94 parsed_value = int(value)
95 else:
96 logger.warning(CANNOT_PARSE_WARNING, value)
97 parsed_value = value
98 relative_key = (path + "." + key)[base_len:]
99 gsettings[relative_key] = parsed_value
100 mode = gsettings["mode"]
101 if mode == "none":
102 settings = {}
103 elif mode == "manual":
104 settings = {}
105 for scheme in ["http", "https"]:
106 scheme_settings = parse_manual_proxy_settings(scheme, gsettings)
107 if scheme_settings is not None:
108 settings[scheme] = scheme_settings
109 else:
110 # If mode is automatic the PAC javascript should be interpreted
111 # on each request. That is out of scope so it's ignored for now
112 settings = {}
113
114 return settings
1150
=== removed file 'ubuntuone/utils/tests/test_gsettings.py'
--- ubuntuone/utils/tests/test_gsettings.py 2017-01-07 18:51:07 +0000
+++ ubuntuone/utils/tests/test_gsettings.py 1970-01-01 00:00:00 +0000
@@ -1,322 +0,0 @@
1# -*- coding: utf-8 -*-
2#
3# Copyright 2011-2012 Canonical Ltd.
4# Copyright 2015-2017 Chicharreros (https://launchpad.net/~chicharreros)
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18# In addition, as a special exception, the copyright holders give
19# permission to link the code of portions of this program with the
20# OpenSSL library under certain conditions as described in each
21# individual source file, and distribute linked combinations
22# including the two.
23# You must obey the GNU General Public License in all respects
24# for all of the code used other than OpenSSL. If you modify
25# file(s) with this exception, you may extend this exception to your
26# version of the file(s), but you are not obligated to do so. If you
27# do not wish to do so, delete this exception statement from your
28# version. If you delete this exception statement from all source
29# files in the program, then also delete it here.
30
31"""Test the gsettings parser."""
32
33import logging
34
35from twisted.trial.unittest import TestCase
36from ubuntuone.devtools.handlers import MementoHandler
37
38from ubuntuone.utils import gsettings
39
40TEMPLATE_GSETTINGS_OUTPUT = """\
41org.gnome.system.proxy autoconfig-url '{autoconfig_url}'
42org.gnome.system.proxy ignore-hosts {ignore_hosts:s}
43org.gnome.system.proxy mode '{mode}'
44org.gnome.system.proxy.ftp host '{ftp_host}'
45org.gnome.system.proxy.ftp port {ftp_port}
46org.gnome.system.proxy.http authentication-password '{auth_password}'
47org.gnome.system.proxy.http authentication-user '{auth_user}'
48org.gnome.system.proxy.http host '{http_host}'
49org.gnome.system.proxy.http port {http_port}
50org.gnome.system.proxy.http use-authentication {http_use_auth}
51org.gnome.system.proxy.https host '{https_host}'
52org.gnome.system.proxy.https port {https_port}
53org.gnome.system.proxy.socks host '{socks_host}'
54org.gnome.system.proxy.socks port {socks_port}
55"""
56
57BASE_GSETTINGS_VALUES = {
58 "autoconfig_url": "",
59 "ignore_hosts": ["localhost", "127.0.0.0/8"],
60 "mode": "none",
61 "ftp_host": "",
62 "ftp_port": 0,
63 "auth_password": "",
64 "auth_user": "",
65 "http_host": "",
66 "http_port": 0,
67 "http_use_auth": "false",
68 "https_host": "",
69 "https_port": 0,
70 "socks_host": "",
71 "socks_port": 0,
72}
73
74
75class ProxySettingsTestCase(TestCase):
76 """Test the getting of the proxy settings."""
77
78 def test_gsettings_cmdline_correct(self):
79 """The command line used to get the proxy settings is the right one."""
80 expected = "gsettings list-recursively org.gnome.system.proxy".split()
81 called = []
82
83 def append_output(args):
84 """Append the output and return some settings."""
85 called.append(args)
86 return TEMPLATE_GSETTINGS_OUTPUT.format(**BASE_GSETTINGS_VALUES)
87
88 self.patch(gsettings.subprocess, "check_output", append_output)
89 gsettings.get_proxy_settings()
90 self.assertEqual(called[0], expected)
91
92 def test_gsettings_parser_none(self):
93 """Test a parser of gsettings."""
94 expected = {}
95 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**BASE_GSETTINGS_VALUES)
96 self.patch(gsettings.subprocess, "check_output",
97 lambda _: fake_output)
98 ps = gsettings.get_proxy_settings()
99 self.assertEqual(ps, expected)
100
101 def _assert_parser_anonymous(self, scheme):
102 """Assert the parsing of anonymous settings."""
103 template_values = dict(BASE_GSETTINGS_VALUES)
104 expected_host = "expected_host"
105 expected_port = 54321
106 expected = {
107 "host": expected_host,
108 "port": expected_port,
109 }
110 template_values.update({
111 "mode": "manual",
112 scheme + "_host": expected_host,
113 scheme + "_port": expected_port,
114 })
115 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
116 self.patch(gsettings.subprocess, "check_output",
117 lambda _: fake_output)
118 ps = gsettings.get_proxy_settings()
119 self.assertEqual(ps[scheme], expected)
120
121 def test_gsettings_parser_http_anonymous(self):
122 """Test a parser of gsettings."""
123 self._assert_parser_anonymous('http')
124
125 def test_gsettings_parser_https_anonymus(self):
126 """Test a parser of gsettings."""
127 self._assert_parser_anonymous('https')
128
129 def test_gsettings_empty_ignore_hosts(self):
130 """Missing values in the ignore hosts."""
131 troublesome_value = "@as []"
132 template_values = dict(BASE_GSETTINGS_VALUES)
133 template_values["ignore_hosts"] = troublesome_value
134 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
135 self.patch(gsettings.subprocess, "check_output",
136 lambda _: fake_output)
137 ps = gsettings.get_proxy_settings()
138 self.assertEqual(ps, {})
139
140 def test_gsettings_cannot_parse(self):
141 """Some weird setting that cannot be parsed is logged with warning."""
142 memento = MementoHandler()
143 memento.setLevel(logging.DEBUG)
144 gsettings.logger.addHandler(memento)
145 self.addCleanup(gsettings.logger.removeHandler, memento)
146
147 troublesome_value = "#bang"
148 template_values = dict(BASE_GSETTINGS_VALUES)
149 template_values["ignore_hosts"] = troublesome_value
150 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
151 self.patch(gsettings.subprocess, "check_output",
152 lambda _: fake_output)
153 ps = gsettings.get_proxy_settings()
154 self.assertTrue(memento.check_warning(gsettings.CANNOT_PARSE_WARNING %
155 troublesome_value))
156 self.assertEqual(ps, {})
157
158 def test_gsettings_parser_http_authenticated(self):
159 """Test a parser of gsettings."""
160 template_values = dict(BASE_GSETTINGS_VALUES)
161 expected_host = "expected_host"
162 expected_port = 54321
163 expected_user = "carlitos"
164 expected_password = "very secret password"
165 expected = {
166 "host": expected_host,
167 "port": expected_port,
168 "username": expected_user,
169 "password": expected_password,
170 }
171 template_values.update({
172 "mode": "manual",
173 "http_host": expected_host,
174 "http_port": expected_port,
175 "auth_user": expected_user,
176 "auth_password": expected_password,
177 "http_use_auth": "true",
178 })
179 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
180 self.patch(gsettings.subprocess, "check_output",
181 lambda _: fake_output)
182 ps = gsettings.get_proxy_settings()
183 self.assertEqual(ps["http"], expected)
184
185 def _assert_parser_authenticated_url(self, scheme):
186 """Test a parser of gsettings with creds in the url."""
187 template_values = dict(BASE_GSETTINGS_VALUES)
188 expected_host = "expected_host"
189 expected_port = 54321
190 expected_user = "carlitos"
191 expected_password = "very secret password"
192 composed_url = '%s:%s@%s' % (expected_user, expected_password,
193 expected_host)
194 expected = {
195 "host": expected_host,
196 "port": expected_port,
197 "username": expected_user,
198 "password": expected_password,
199 }
200 template_values.update({
201 "mode": "manual",
202 scheme + "_host": composed_url,
203 scheme + "_port": expected_port,
204 "http_use_auth": "false",
205 })
206 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
207 self.patch(gsettings.subprocess, "check_output",
208 lambda _: fake_output)
209 ps = gsettings.get_proxy_settings()
210 self.assertEqual(ps[scheme], expected)
211
212 def test_gsettings_parser_http_authenticated_url(self):
213 """Test a parser of gsettings with creds in the url."""
214 self._assert_parser_authenticated_url('http')
215
216 def test_gsettings_parser_https_authenticated_url(self):
217 """Test a parser of gsettings with creds in the url."""
218 self._assert_parser_authenticated_url('https')
219
220 def test_gsettings_auth_over_url(self):
221 """Test that the settings are more important that the url."""
222 template_values = dict(BASE_GSETTINGS_VALUES)
223 expected_host = "expected_host"
224 expected_port = 54321
225 expected_user = "carlitos"
226 expected_password = "very secret password"
227 composed_url = '%s:%s@%s' % ('user', 'random',
228 expected_host)
229 http_expected = {
230 "host": expected_host,
231 "port": expected_port,
232 "username": expected_user,
233 "password": expected_password,
234 }
235 template_values.update({
236 "mode": "manual",
237 "http_host": composed_url,
238 "http_port": expected_port,
239 "auth_user": expected_user,
240 "auth_password": expected_password,
241 "http_use_auth": "true",
242 })
243 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
244 self.patch(gsettings.subprocess, "check_output",
245 lambda _: fake_output)
246 ps = gsettings.get_proxy_settings()
247 self.assertEqual(ps["http"], http_expected)
248
249 def _assert_parser_empty_url(self, scheme):
250 """Assert the parsing of an empty url."""
251 template_values = dict(BASE_GSETTINGS_VALUES)
252 template_values.update({
253 "mode": "manual",
254 scheme + "_host": '',
255 scheme + "_port": 0,
256 "http_use_auth": "false",
257 })
258 fake_output = TEMPLATE_GSETTINGS_OUTPUT.format(**template_values)
259 self.patch(gsettings.subprocess, "check_output",
260 lambda _: fake_output)
261 ps = gsettings.get_proxy_settings()
262 self.assertNotIn(scheme, ps)
263
264 def test_gsettings_parser_empty_http_url(self):
265 """Test when there is no http proxy set."""
266 self._assert_parser_empty_url('http')
267
268 def test_gsettings_parser_empty_https_url(self):
269 """Test when there is no https proxy set."""
270 self._assert_parser_empty_url('https')
271
272
273class ParseProxyHostTestCase(TestCase):
274 """Test the parsing of the domain."""
275
276 def test_onlyhost(self):
277 """Parse a host with no username or password."""
278 sample = "hostname"
279 hostname, username, password = gsettings.parse_proxy_host(sample)
280 self.assertEqual(username, None)
281 self.assertEqual(password, None)
282 self.assertEqual(hostname, "hostname")
283
284 def test_user_and_host(self):
285 """Parse host just with the username."""
286 sample = "username@hostname"
287 hostname, username, password = gsettings.parse_proxy_host(sample)
288 self.assertEqual(username, "username")
289 self.assertEqual(password, None)
290 self.assertEqual(hostname, "hostname")
291
292 def test_user_pass_and_host(self):
293 """Test parsing a host with a username and password."""
294 sample = "username:password@hostname"
295 hostname, username, password = gsettings.parse_proxy_host(sample)
296 self.assertEqual(username, "username")
297 self.assertEqual(password, "password")
298 self.assertEqual(hostname, "hostname")
299
300 def test_username_with_at(self):
301 """Test parsing the host with a username with @."""
302 sample = "username@company.com:password@hostname"
303 hostname, username, password = gsettings.parse_proxy_host(sample)
304 self.assertEqual(username, "username@company.com")
305 self.assertEqual(password, "password")
306 self.assertEqual(hostname, "hostname")
307
308 def test_username_with_at_nopass(self):
309 """Test parsing the host without a password."""
310 sample = "username@company.com@hostname"
311 hostname, username, password = gsettings.parse_proxy_host(sample)
312 self.assertEqual(username, "username@company.com")
313 self.assertEqual(password, None)
314 self.assertEqual(hostname, "hostname")
315
316 def test_user_pass_with_colon_and_host(self):
317 """Test parsing the host with a password that contains :."""
318 sample = "username:pass:word@hostname"
319 hostname, username, password = gsettings.parse_proxy_host(sample)
320 self.assertEqual(username, "username")
321 self.assertEqual(password, "pass:word")
322 self.assertEqual(hostname, "hostname")

Subscribers

People subscribed via source and target branches

to all changes: