Merge lp:~alecu/ubuntuone-client/proxy-tunnel-cookies into lp:ubuntuone-client
- proxy-tunnel-cookies
- Merge into trunk
Proposed by
Alejandro J. Cura
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Alejandro J. Cura | ||||
Approved revision: | 1240 | ||||
Merged at revision: | 1212 | ||||
Proposed branch: | lp:~alecu/ubuntuone-client/proxy-tunnel-cookies | ||||
Merge into: | lp:ubuntuone-client | ||||
Prerequisite: | lp:~alecu/ubuntuone-client/proxy-tunnel-auth | ||||
Diff against target: |
480 lines (+121/-36) 7 files modified
tests/proxy/__init__.py (+2/-0) tests/proxy/test_tunnel_client.py (+22/-8) tests/proxy/test_tunnel_server.py (+39/-9) tests/syncdaemon/test_tunnel_runner.py (+6/-2) ubuntuone/proxy/common.py (+2/-0) ubuntuone/proxy/tunnel_client.py (+26/-11) ubuntuone/proxy/tunnel_server.py (+24/-6) |
||||
To merge this branch: | bzr merge lp:~alecu/ubuntuone-client/proxy-tunnel-cookies | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Manuel de la Peña (community) | Approve | ||
Diego Sarmentero (community) | Approve | ||
Review via email: mp+97791@code.launchpad.net |
Commit message
- Only allow connections that provide the right cookie thru the tunnel (LP: #929207).
Description of the change
- Only allow connections that provide the right cookie thru the tunnel (LP: #929207).
To post a comment you must log in.
- 1240. By Alejandro J. Cura
-
Merged proxy-tunnel-auth into proxy-tunnel-
cookies.
Revision history for this message
Manuel de la Peña (mandel) wrote : | # |
Looks good from spain.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'tests/proxy/__init__.py' | |||
2 | --- tests/proxy/__init__.py 2012-02-24 02:30:57 +0000 | |||
3 | +++ tests/proxy/__init__.py 2012-03-16 13:44:20 +0000 | |||
4 | @@ -24,6 +24,7 @@ | |||
5 | 24 | SIMPLERESOURCE = "simpleresource" | 24 | SIMPLERESOURCE = "simpleresource" |
6 | 25 | DUMMY_KEY_FILENAME = "dummy.key" | 25 | DUMMY_KEY_FILENAME = "dummy.key" |
7 | 26 | DUMMY_CERT_FILENAME = "dummy.cert" | 26 | DUMMY_CERT_FILENAME = "dummy.cert" |
8 | 27 | FAKE_COOKIE = "fa:ke:co:ok:ie" | ||
9 | 27 | 28 | ||
10 | 28 | 29 | ||
11 | 29 | class SaveHTTPChannel(http.HTTPChannel): | 30 | class SaveHTTPChannel(http.HTTPChannel): |
12 | @@ -137,6 +138,7 @@ | |||
13 | 137 | 138 | ||
14 | 138 | connected = True | 139 | connected = True |
15 | 139 | disconnecting = False | 140 | disconnecting = False |
16 | 141 | cookie = None | ||
17 | 140 | 142 | ||
18 | 141 | def loseConnection(self): | 143 | def loseConnection(self): |
19 | 142 | """Mark the connection as lost.""" | 144 | """Mark the connection as lost.""" |
20 | 143 | 145 | ||
21 | === modified file 'tests/proxy/test_tunnel_client.py' | |||
22 | --- tests/proxy/test_tunnel_client.py 2012-03-14 17:38:31 +0000 | |||
23 | +++ tests/proxy/test_tunnel_client.py 2012-03-16 13:44:20 +0000 | |||
24 | @@ -23,6 +23,7 @@ | |||
25 | 23 | 23 | ||
26 | 24 | from tests.proxy import ( | 24 | from tests.proxy import ( |
27 | 25 | FakeTransport, | 25 | FakeTransport, |
28 | 26 | FAKE_COOKIE, | ||
29 | 26 | MockWebServer, | 27 | MockWebServer, |
30 | 27 | SAMPLE_CONTENT, | 28 | SAMPLE_CONTENT, |
31 | 28 | SIMPLERESOURCE, | 29 | SIMPLERESOURCE, |
32 | @@ -72,11 +73,12 @@ | |||
33 | 72 | yield super(TunnelClientProtocolTestCase, self).setUp() | 73 | yield super(TunnelClientProtocolTestCase, self).setUp() |
34 | 73 | self.host, self.port = "9.9.9.9", 8765 | 74 | self.host, self.port = "9.9.9.9", 8765 |
35 | 74 | fake_addr = object() | 75 | fake_addr = object() |
36 | 76 | self.cookie = FAKE_COOKIE | ||
37 | 75 | self.other_proto = SavingProtocol() | 77 | self.other_proto = SavingProtocol() |
38 | 76 | other_factory = protocol.ClientFactory() | 78 | other_factory = protocol.ClientFactory() |
39 | 77 | other_factory.buildProtocol = lambda _addr: self.other_proto | 79 | other_factory.buildProtocol = lambda _addr: self.other_proto |
40 | 78 | tunnel_client_factory = tunnel_client.TunnelClientFactory(self.host, | 80 | tunnel_client_factory = tunnel_client.TunnelClientFactory(self.host, |
42 | 79 | self.port, other_factory) | 81 | self.port, other_factory, self.cookie) |
43 | 80 | tunnel_client_proto = tunnel_client_factory.buildProtocol(fake_addr) | 82 | tunnel_client_proto = tunnel_client_factory.buildProtocol(fake_addr) |
44 | 81 | tunnel_client_proto.transport = FakeTransport() | 83 | tunnel_client_proto.transport = FakeTransport() |
45 | 82 | tunnel_client_proto.connectionMade() | 84 | tunnel_client_proto.connectionMade() |
46 | @@ -91,6 +93,13 @@ | |||
47 | 91 | self.assertTrue(written.endswith(CRLF * 2), | 93 | self.assertTrue(written.endswith(CRLF * 2), |
48 | 92 | "Ends with a double CRLF") | 94 | "Ends with a double CRLF") |
49 | 93 | 95 | ||
50 | 96 | def test_sends_cookie_header(self): | ||
51 | 97 | """Sends the expected cookie header.""" | ||
52 | 98 | expected = "%s: %s" % (tunnel_client.TUNNEL_COOKIE_HEADER, self.cookie) | ||
53 | 99 | written = self.tunnel_client_proto.transport.getvalue() | ||
54 | 100 | headers = written.split(CRLF)[1:] | ||
55 | 101 | self.assertIn(expected, headers) | ||
56 | 102 | |||
57 | 94 | def test_handles_successful_connection(self): | 103 | def test_handles_successful_connection(self): |
58 | 95 | """A successful connection is handled.""" | 104 | """A successful connection is handled.""" |
59 | 96 | self.tunnel_client_proto.dataReceived(FAKE_HEADER) | 105 | self.tunnel_client_proto.dataReceived(FAKE_HEADER) |
60 | @@ -132,7 +141,8 @@ | |||
61 | 132 | def test_forwards_started(self): | 141 | def test_forwards_started(self): |
62 | 133 | """The factory forwards the startedConnecting call.""" | 142 | """The factory forwards the startedConnecting call.""" |
63 | 134 | fake_other_factory = FakeOtherFactory() | 143 | fake_other_factory = FakeOtherFactory() |
65 | 135 | tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory) | 144 | tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory, |
66 | 145 | FAKE_COOKIE) | ||
67 | 136 | fake_connector = object() | 146 | fake_connector = object() |
68 | 137 | tcf.startedConnecting(fake_connector) | 147 | tcf.startedConnecting(fake_connector) |
69 | 138 | self.assertEqual(fake_other_factory.started_called, (fake_connector,)) | 148 | self.assertEqual(fake_other_factory.started_called, (fake_connector,)) |
70 | @@ -141,7 +151,8 @@ | |||
71 | 141 | """The factory forwards the clientConnectionFailed call.""" | 151 | """The factory forwards the clientConnectionFailed call.""" |
72 | 142 | fake_reason = object() | 152 | fake_reason = object() |
73 | 143 | fake_other_factory = FakeOtherFactory() | 153 | fake_other_factory = FakeOtherFactory() |
75 | 144 | tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory) | 154 | tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory, |
76 | 155 | FAKE_COOKIE) | ||
77 | 145 | fake_connector = object() | 156 | fake_connector = object() |
78 | 146 | tcf.clientConnectionFailed(fake_connector, fake_reason) | 157 | tcf.clientConnectionFailed(fake_connector, fake_reason) |
79 | 147 | self.assertEqual(fake_other_factory.failed_called, | 158 | self.assertEqual(fake_other_factory.failed_called, |
80 | @@ -151,7 +162,8 @@ | |||
81 | 151 | """The factory forwards the clientConnectionLost call.""" | 162 | """The factory forwards the clientConnectionLost call.""" |
82 | 152 | fake_reason = object() | 163 | fake_reason = object() |
83 | 153 | fake_other_factory = FakeOtherFactory() | 164 | fake_other_factory = FakeOtherFactory() |
85 | 154 | tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory) | 165 | tcf = tunnel_client.TunnelClientFactory(None, None, fake_other_factory, |
86 | 166 | FAKE_COOKIE) | ||
87 | 155 | fake_connector = object() | 167 | fake_connector = object() |
88 | 156 | tcf.clientConnectionLost(fake_connector, fake_reason) | 168 | tcf.clientConnectionLost(fake_connector, fake_reason) |
89 | 157 | self.assertEqual(fake_other_factory.lost_called, | 169 | self.assertEqual(fake_other_factory.lost_called, |
90 | @@ -172,13 +184,15 @@ | |||
91 | 172 | self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE | 184 | self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE |
92 | 173 | self.dest_ssl_url = (self.ws.get_ssl_iri().encode("utf-8") + | 185 | self.dest_ssl_url = (self.ws.get_ssl_iri().encode("utf-8") + |
93 | 174 | SIMPLERESOURCE) | 186 | SIMPLERESOURCE) |
95 | 175 | self.tunnel_server = TunnelServer() | 187 | self.cookie = FAKE_COOKIE |
96 | 188 | self.tunnel_server = TunnelServer(self.cookie) | ||
97 | 176 | self.addCleanup(self.tunnel_server.shutdown) | 189 | self.addCleanup(self.tunnel_server.shutdown) |
98 | 177 | 190 | ||
99 | 178 | @defer.inlineCallbacks | 191 | @defer.inlineCallbacks |
100 | 179 | def test_connects_right(self): | 192 | def test_connects_right(self): |
101 | 180 | """Uses the CONNECT method on the tunnel.""" | 193 | """Uses the CONNECT method on the tunnel.""" |
103 | 181 | tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port) | 194 | tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port, |
104 | 195 | self.cookie) | ||
105 | 182 | factory = client.HTTPClientFactory(self.dest_url) | 196 | factory = client.HTTPClientFactory(self.dest_url) |
106 | 183 | scheme, host, port, path = client._parse(self.dest_url) | 197 | scheme, host, port, path = client._parse(self.dest_url) |
107 | 184 | tunnel_client.connectTCP(host, port, factory) | 198 | tunnel_client.connectTCP(host, port, factory) |
108 | @@ -188,8 +202,8 @@ | |||
109 | 188 | @defer.inlineCallbacks | 202 | @defer.inlineCallbacks |
110 | 189 | def test_starts_tls_connection(self): | 203 | def test_starts_tls_connection(self): |
111 | 190 | """TLS is started after connecting; control passed to the client.""" | 204 | """TLS is started after connecting; control passed to the client.""" |
114 | 191 | tunnel_client = TunnelClient("0.0.0.0", | 205 | tunnel_client = TunnelClient("0.0.0.0", self.tunnel_server.port, |
115 | 192 | self.tunnel_server.port) | 206 | self.cookie) |
116 | 193 | factory = client.HTTPClientFactory(self.dest_ssl_url) | 207 | factory = client.HTTPClientFactory(self.dest_ssl_url) |
117 | 194 | scheme, host, port, path = client._parse(self.dest_ssl_url) | 208 | scheme, host, port, path = client._parse(self.dest_ssl_url) |
118 | 195 | context_factory = ssl.ClientContextFactory() | 209 | context_factory = ssl.ClientContextFactory() |
119 | 196 | 210 | ||
120 | === modified file 'tests/proxy/test_tunnel_server.py' | |||
121 | --- tests/proxy/test_tunnel_server.py 2012-03-16 13:44:20 +0000 | |||
122 | +++ tests/proxy/test_tunnel_server.py 2012-03-16 13:44:20 +0000 | |||
123 | @@ -26,6 +26,7 @@ | |||
124 | 26 | 26 | ||
125 | 27 | from tests.proxy import ( | 27 | from tests.proxy import ( |
126 | 28 | FakeTransport, | 28 | FakeTransport, |
127 | 29 | FAKE_COOKIE, | ||
128 | 29 | MockWebServer, | 30 | MockWebServer, |
129 | 30 | SAMPLE_CONTENT, | 31 | SAMPLE_CONTENT, |
130 | 31 | SIMPLERESOURCE, | 32 | SIMPLERESOURCE, |
131 | @@ -38,6 +39,7 @@ | |||
132 | 38 | "CONNECT %s HTTP/1.0" + CRLF + | 39 | "CONNECT %s HTTP/1.0" + CRLF + |
133 | 39 | "Header1: value1" + CRLF + | 40 | "Header1: value1" + CRLF + |
134 | 40 | "Header2: value2" + CRLF + | 41 | "Header2: value2" + CRLF + |
135 | 42 | tunnel_server.TUNNEL_COOKIE_HEADER + ": %s" + CRLF + | ||
136 | 41 | CRLF + | 43 | CRLF + |
137 | 42 | "GET %s HTTP/1.0" + CRLF + CRLF | 44 | "GET %s HTTP/1.0" + CRLF + CRLF |
138 | 43 | ) | 45 | ) |
139 | @@ -130,7 +132,8 @@ | |||
140 | 130 | self.ws = MockWebServer() | 132 | self.ws = MockWebServer() |
141 | 131 | self.addCleanup(self.ws.stop) | 133 | self.addCleanup(self.ws.stop) |
142 | 132 | self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE | 134 | self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE |
144 | 133 | self.tunnel_server = tunnel_server.TunnelServer() | 135 | self.cookie = FAKE_COOKIE |
145 | 136 | self.tunnel_server = tunnel_server.TunnelServer(self.cookie) | ||
146 | 134 | self.addCleanup(self.tunnel_server.shutdown) | 137 | self.addCleanup(self.tunnel_server.shutdown) |
147 | 135 | 138 | ||
148 | 136 | def test_init(self): | 139 | def test_init(self): |
149 | @@ -148,7 +151,8 @@ | |||
150 | 148 | def test_complete_connection(self): | 151 | def test_complete_connection(self): |
151 | 149 | """Test from the tunnel server down.""" | 152 | """Test from the tunnel server down.""" |
152 | 150 | url = urlparse(self.dest_url) | 153 | url = urlparse(self.dest_url) |
154 | 151 | fake_session = FAKE_SESSION_TEMPLATE % (url.netloc, url.path) | 154 | fake_session = FAKE_SESSION_TEMPLATE % ( |
155 | 155 | url.netloc, self.cookie, url.path) | ||
156 | 152 | client = FakeClientFactory(fake_session) | 156 | client = FakeClientFactory(fake_session) |
157 | 153 | reactor.connectTCP("0.0.0.0", self.tunnel_server.port, client) | 157 | reactor.connectTCP("0.0.0.0", self.tunnel_server.port, client) |
158 | 154 | response = yield client.response | 158 | response = yield client.response |
159 | @@ -196,11 +200,14 @@ | |||
160 | 196 | self.addCleanup(self.ws.stop) | 200 | self.addCleanup(self.ws.stop) |
161 | 197 | self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE | 201 | self.dest_url = self.ws.get_iri().encode("utf-8") + SIMPLERESOURCE |
162 | 198 | self.transport = FakeTransport() | 202 | self.transport = FakeTransport() |
163 | 203 | self.transport.cookie = FAKE_COOKIE | ||
164 | 199 | self.fake_client = FakeClient() | 204 | self.fake_client = FakeClient() |
165 | 200 | self.proto = tunnel_server.ServerTunnelProtocol( | 205 | self.proto = tunnel_server.ServerTunnelProtocol( |
166 | 201 | lambda _: self.fake_client) | 206 | lambda _: self.fake_client) |
167 | 202 | self.fake_client.protocol = self.proto | 207 | self.fake_client.protocol = self.proto |
168 | 203 | self.proto.transport = self.transport | 208 | self.proto.transport = self.transport |
169 | 209 | self.cookie_line = "%s: %s" % (tunnel_server.TUNNEL_COOKIE_HEADER, | ||
170 | 210 | FAKE_COOKIE) | ||
171 | 204 | 211 | ||
172 | 205 | def test_broken_request(self): | 212 | def test_broken_request(self): |
173 | 206 | """Broken request.""" | 213 | """Broken request.""" |
174 | @@ -223,7 +230,8 @@ | |||
175 | 223 | def test_connection_is_established(self): | 230 | def test_connection_is_established(self): |
176 | 224 | """The response code is sent.""" | 231 | """The response code is sent.""" |
177 | 225 | expected = "HTTP/1.0 200 Proxy connection established" + CRLF | 232 | expected = "HTTP/1.0 200 Proxy connection established" + CRLF |
179 | 226 | self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF * 2) | 233 | self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF + |
180 | 234 | self.cookie_line + CRLF * 2) | ||
181 | 227 | self.assertTrue(self.transport.getvalue().startswith(expected), | 235 | self.assertTrue(self.transport.getvalue().startswith(expected), |
182 | 228 | "First line must be the response status") | 236 | "First line must be the response status") |
183 | 229 | 237 | ||
184 | @@ -232,7 +240,8 @@ | |||
185 | 232 | error = tunnel_server.ConnectionError() | 240 | error = tunnel_server.ConnectionError() |
186 | 233 | self.patch(self.fake_client, "connection_result", defer.fail(error)) | 241 | self.patch(self.fake_client, "connection_result", defer.fail(error)) |
187 | 234 | expected = "HTTP/1.0 500 Connection error" + CRLF | 242 | expected = "HTTP/1.0 500 Connection error" + CRLF |
189 | 235 | self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF * 2) | 243 | self.proto.dataReceived("CONNECT 127.0.0.1:9999 HTTP/1.0" + CRLF + |
190 | 244 | self.cookie_line + CRLF * 2) | ||
191 | 236 | self.assertTrue(self.transport.getvalue().startswith(expected), | 245 | self.assertTrue(self.transport.getvalue().startswith(expected), |
192 | 237 | "The connection should fail at this point.") | 246 | "The connection should fail at this point.") |
193 | 238 | 247 | ||
194 | @@ -247,10 +256,25 @@ | |||
195 | 247 | "Header2: value2" + CRLF + CRLF) | 256 | "Header2: value2" + CRLF + CRLF) |
196 | 248 | self.assertEqual(self.proto.received_headers, expected) | 257 | self.assertEqual(self.proto.received_headers, expected) |
197 | 249 | 258 | ||
198 | 259 | def test_cookie_header_present(self): | ||
199 | 260 | """The cookie header must be present.""" | ||
200 | 261 | self.proto.received_headers = [ | ||
201 | 262 | (tunnel_server.TUNNEL_COOKIE_HEADER, FAKE_COOKIE), | ||
202 | 263 | ] | ||
203 | 264 | self.proto.verify_cookie() | ||
204 | 265 | |||
205 | 266 | def test_cookie_header_absent(self): | ||
206 | 267 | """The tunnel should refuse connections without the cookie.""" | ||
207 | 268 | self.proto.received_headers = [] | ||
208 | 269 | exception = self.assertRaises(tunnel_server.ConnectionError, | ||
209 | 270 | self.proto.verify_cookie) | ||
210 | 271 | self.assertEqual(exception.code, 418) | ||
211 | 272 | |||
212 | 250 | def test_successful_connect(self): | 273 | def test_successful_connect(self): |
213 | 251 | """A successful connect thru the tunnel.""" | 274 | """A successful connect thru the tunnel.""" |
214 | 252 | url = urlparse(self.dest_url) | 275 | url = urlparse(self.dest_url) |
216 | 253 | data = FAKE_SESSION_TEMPLATE % (url.netloc, url.path) | 276 | data = FAKE_SESSION_TEMPLATE % (url.netloc, self.transport.cookie, |
217 | 277 | url.path) | ||
218 | 254 | self.proto.dataReceived(data) | 278 | self.proto.dataReceived(data) |
219 | 255 | lines = self.transport.getvalue().split(CRLF) | 279 | lines = self.transport.getvalue().split(CRLF) |
220 | 256 | self.assertEqual(lines[-1], SAMPLE_CONTENT) | 280 | self.assertEqual(lines[-1], SAMPLE_CONTENT) |
221 | @@ -264,6 +288,7 @@ | |||
222 | 264 | def test_keyring_credentials_are_retried(self): | 288 | def test_keyring_credentials_are_retried(self): |
223 | 265 | """Wrong credentials are retried with values from keyring.""" | 289 | """Wrong credentials are retried with values from keyring.""" |
224 | 266 | self.fake_client.check_credentials = True | 290 | self.fake_client.check_credentials = True |
225 | 291 | self.patch(self.proto, "verify_cookie", lambda: None) | ||
226 | 267 | self.patch(self.proto, "error_response", | 292 | self.patch(self.proto, "error_response", |
227 | 268 | lambda code, desc: self.fail(desc)) | 293 | lambda code, desc: self.fail(desc)) |
228 | 269 | self.proto.proxy_domain = "xxx" | 294 | self.proto.proxy_domain = "xxx" |
229 | @@ -610,12 +635,17 @@ | |||
230 | 610 | tunnel_server.main([]) | 635 | tunnel_server.main([]) |
231 | 611 | self.assertEqual(len(self.called), 1) | 636 | self.assertEqual(len(self.called), 1) |
232 | 612 | 637 | ||
234 | 613 | def test_on_proxies_enabled_prints_port(self): | 638 | def test_on_proxies_enabled_prints_port_and_cookie(self): |
235 | 614 | """With proxies enabled print port to stdout and start the mainloop.""" | 639 | """With proxies enabled print port to stdout and start the mainloop.""" |
236 | 640 | self.patch(tunnel_server.uuid, "uuid4", lambda: FAKE_COOKIE) | ||
237 | 615 | self.proxies_enabled = True | 641 | self.proxies_enabled = True |
241 | 616 | tunnel_server.main(["example.com", "443"]) | 642 | port = 443 |
242 | 617 | self.assertIn(tunnel_server.TUNNEL_PORT_LABEL + ":", | 643 | tunnel_server.main(["example.com", str(port)]) |
243 | 618 | self.fake_stdout.getvalue()) | 644 | stdout = self.fake_stdout.getvalue() |
244 | 645 | |||
245 | 646 | self.assertIn(tunnel_server.TUNNEL_PORT_LABEL + ": ", stdout) | ||
246 | 647 | cookie_line = tunnel_server.TUNNEL_COOKIE_LABEL + ": " + FAKE_COOKIE | ||
247 | 648 | self.assertIn(cookie_line, stdout) | ||
248 | 619 | 649 | ||
249 | 620 | def test_on_proxies_disabled_exit(self): | 650 | def test_on_proxies_disabled_exit(self): |
250 | 621 | """With proxies disabled, print a message and exit gracefully.""" | 651 | """With proxies disabled, print a message and exit gracefully.""" |
251 | 622 | 652 | ||
252 | === modified file 'tests/syncdaemon/test_tunnel_runner.py' | |||
253 | --- tests/syncdaemon/test_tunnel_runner.py 2012-03-12 17:42:57 +0000 | |||
254 | +++ tests/syncdaemon/test_tunnel_runner.py 2012-03-16 13:44:20 +0000 | |||
255 | @@ -18,6 +18,7 @@ | |||
256 | 18 | from twisted.internet import defer, error, reactor, task | 18 | from twisted.internet import defer, error, reactor, task |
257 | 19 | from twisted.trial.unittest import TestCase | 19 | from twisted.trial.unittest import TestCase |
258 | 20 | 20 | ||
259 | 21 | from tests.proxy import FAKE_COOKIE | ||
260 | 21 | from ubuntuone.proxy import tunnel_client | 22 | from ubuntuone.proxy import tunnel_client |
261 | 22 | from ubuntuone.syncdaemon import tunnel_runner | 23 | from ubuntuone.syncdaemon import tunnel_runner |
262 | 23 | 24 | ||
263 | @@ -106,9 +107,12 @@ | |||
264 | 106 | self.assertEqual(client, clock) | 107 | self.assertEqual(client, clock) |
265 | 107 | 108 | ||
266 | 108 | @defer.inlineCallbacks | 109 | @defer.inlineCallbacks |
268 | 109 | def test_tunnel_process_prints_port_number(self): | 110 | def test_tunnel_process_prints_port_number_and_cookie(self): |
269 | 110 | """The tunnel process prints the port number.""" | 111 | """The tunnel process prints the port number.""" |
271 | 111 | received = "%s: %d\n" % (tunnel_client.TUNNEL_PORT_LABEL, FAKE_PORT) | 112 | received = "%s: %d\n%s: %s\n" % ( |
272 | 113 | tunnel_client.TUNNEL_PORT_LABEL, FAKE_PORT, | ||
273 | 114 | tunnel_client.TUNNEL_COOKIE_LABEL, FAKE_COOKIE) | ||
274 | 112 | self.process_protocol.outReceived(received) | 115 | self.process_protocol.outReceived(received) |
275 | 113 | client = yield self.tr.get_client() | 116 | client = yield self.tr.get_client() |
276 | 114 | self.assertEqual(client.tunnel_port, FAKE_PORT) | 117 | self.assertEqual(client.tunnel_port, FAKE_PORT) |
277 | 118 | self.assertEqual(client.cookie, FAKE_COOKIE) | ||
278 | 115 | 119 | ||
279 | === modified file 'ubuntuone/proxy/common.py' | |||
280 | --- ubuntuone/proxy/common.py 2012-03-16 13:44:20 +0000 | |||
281 | +++ ubuntuone/proxy/common.py 2012-03-16 13:44:20 +0000 | |||
282 | @@ -19,6 +19,8 @@ | |||
283 | 19 | 19 | ||
284 | 20 | CRLF = "\r\n" | 20 | CRLF = "\r\n" |
285 | 21 | TUNNEL_PORT_LABEL = "Tunnel port" | 21 | TUNNEL_PORT_LABEL = "Tunnel port" |
286 | 22 | TUNNEL_COOKIE_LABEL = "Tunnel cookie" | ||
287 | 23 | TUNNEL_COOKIE_HEADER = "Proxy-Tunnel-Cookie" | ||
288 | 22 | 24 | ||
289 | 23 | 25 | ||
290 | 24 | class BaseTunnelProtocol(basic.LineReceiver): | 26 | class BaseTunnelProtocol(basic.LineReceiver): |
291 | 25 | 27 | ||
292 | === modified file 'ubuntuone/proxy/tunnel_client.py' | |||
293 | --- ubuntuone/proxy/tunnel_client.py 2012-03-14 17:38:31 +0000 | |||
294 | +++ ubuntuone/proxy/tunnel_client.py 2012-03-16 13:44:20 +0000 | |||
295 | @@ -19,7 +19,13 @@ | |||
296 | 19 | 19 | ||
297 | 20 | from twisted.internet import protocol, reactor | 20 | from twisted.internet import protocol, reactor |
298 | 21 | 21 | ||
300 | 22 | from ubuntuone.proxy.common import BaseTunnelProtocol, CRLF, TUNNEL_PORT_LABEL | 22 | from ubuntuone.proxy.common import ( |
301 | 23 | BaseTunnelProtocol, | ||
302 | 24 | CRLF, | ||
303 | 25 | TUNNEL_COOKIE_LABEL, | ||
304 | 26 | TUNNEL_COOKIE_HEADER, | ||
305 | 27 | TUNNEL_PORT_LABEL, | ||
306 | 28 | ) | ||
307 | 23 | 29 | ||
308 | 24 | METHOD_LINE = "CONNECT %s:%d HTTP/1.0" + CRLF | 30 | METHOD_LINE = "CONNECT %s:%d HTTP/1.0" + CRLF |
309 | 25 | LOCALHOST = "127.0.0.1" | 31 | LOCALHOST = "127.0.0.1" |
310 | @@ -34,7 +40,8 @@ | |||
311 | 34 | method_line = METHOD_LINE % (self.factory.tunnel_host, | 40 | method_line = METHOD_LINE % (self.factory.tunnel_host, |
312 | 35 | self.factory.tunnel_port) | 41 | self.factory.tunnel_port) |
313 | 36 | headers = { | 42 | headers = { |
315 | 37 | "User-Agent": "Ubuntu One tunnel client" | 43 | "User-Agent": "Ubuntu One tunnel client", |
316 | 44 | TUNNEL_COOKIE_HEADER: self.factory.cookie, | ||
317 | 38 | } | 45 | } |
318 | 39 | self.transport.write(method_line + | 46 | self.transport.write(method_line + |
319 | 40 | self.format_headers(headers) + | 47 | self.format_headers(headers) + |
320 | @@ -70,13 +77,14 @@ | |||
321 | 70 | 77 | ||
322 | 71 | protocol = TunnelClientProtocol | 78 | protocol = TunnelClientProtocol |
323 | 72 | 79 | ||
325 | 73 | def __init__(self, tunnel_host, tunnel_port, other_factory, | 80 | def __init__(self, tunnel_host, tunnel_port, other_factory, cookie, |
326 | 74 | context_factory=None): | 81 | context_factory=None): |
327 | 75 | """Initialize this factory.""" | 82 | """Initialize this factory.""" |
328 | 76 | self.tunnel_host = tunnel_host | 83 | self.tunnel_host = tunnel_host |
329 | 77 | self.tunnel_port = tunnel_port | 84 | self.tunnel_port = tunnel_port |
330 | 78 | self.other_factory = other_factory | 85 | self.other_factory = other_factory |
331 | 79 | self.context_factory = context_factory | 86 | self.context_factory = context_factory |
332 | 87 | self.cookie = cookie | ||
333 | 80 | 88 | ||
334 | 81 | def startedConnecting(self, connector): | 89 | def startedConnecting(self, connector): |
335 | 82 | """Forward this call to the other factory.""" | 90 | """Forward this call to the other factory.""" |
336 | @@ -94,16 +102,17 @@ | |||
337 | 94 | class TunnelClient(object): | 102 | class TunnelClient(object): |
338 | 95 | """A client for the proxy tunnel.""" | 103 | """A client for the proxy tunnel.""" |
339 | 96 | 104 | ||
341 | 97 | def __init__(self, tunnel_host, tunnel_port): | 105 | def __init__(self, tunnel_host, tunnel_port, cookie): |
342 | 98 | """Initialize this client.""" | 106 | """Initialize this client.""" |
343 | 99 | self.tunnel_host = tunnel_host | 107 | self.tunnel_host = tunnel_host |
344 | 100 | self.tunnel_port = tunnel_port | 108 | self.tunnel_port = tunnel_port |
345 | 109 | self.cookie = cookie | ||
346 | 101 | 110 | ||
347 | 102 | def connectTCP(self, host, port, factory, *args, **kwargs): | 111 | def connectTCP(self, host, port, factory, *args, **kwargs): |
348 | 103 | """A connectTCP going thru the tunnel.""" | 112 | """A connectTCP going thru the tunnel.""" |
349 | 104 | logger.info("Connecting (TCP) to %r:%r via tunnel at %r:%r", | 113 | logger.info("Connecting (TCP) to %r:%r via tunnel at %r:%r", |
350 | 105 | host, port, self.tunnel_host, self.tunnel_port) | 114 | host, port, self.tunnel_host, self.tunnel_port) |
352 | 106 | tunnel_factory = TunnelClientFactory(host, port, factory) | 115 | tunnel_factory = TunnelClientFactory(host, port, factory, self.cookie) |
353 | 107 | return reactor.connectTCP(self.tunnel_host, self.tunnel_port, | 116 | return reactor.connectTCP(self.tunnel_host, self.tunnel_port, |
354 | 108 | tunnel_factory, *args, **kwargs) | 117 | tunnel_factory, *args, **kwargs) |
355 | 109 | 118 | ||
356 | @@ -112,7 +121,7 @@ | |||
357 | 112 | """A connectSSL going thru the tunnel.""" | 121 | """A connectSSL going thru the tunnel.""" |
358 | 113 | logger.info("Connecting (SSL) to %r:%r via tunnel at %r:%r", | 122 | logger.info("Connecting (SSL) to %r:%r via tunnel at %r:%r", |
359 | 114 | host, port, self.tunnel_host, self.tunnel_port) | 123 | host, port, self.tunnel_host, self.tunnel_port) |
361 | 115 | tunnel_factory = TunnelClientFactory(host, port, factory, | 124 | tunnel_factory = TunnelClientFactory(host, port, factory, self.cookie, |
362 | 116 | contextFactory) | 125 | contextFactory) |
363 | 117 | return reactor.connectTCP(self.tunnel_host, self.tunnel_port, | 126 | return reactor.connectTCP(self.tunnel_host, self.tunnel_port, |
364 | 118 | tunnel_factory, *args, **kwargs) | 127 | tunnel_factory, *args, **kwargs) |
365 | @@ -127,6 +136,8 @@ | |||
366 | 127 | """Initialize this protocol.""" | 136 | """Initialize this protocol.""" |
367 | 128 | self.client_d = client_d | 137 | self.client_d = client_d |
368 | 129 | self.timer = None | 138 | self.timer = None |
369 | 139 | self.port = None | ||
370 | 140 | self.cookie = None | ||
371 | 130 | 141 | ||
372 | 131 | def connectionMade(self): | 142 | def connectionMade(self): |
373 | 132 | """The process has started, start a timer.""" | 143 | """The process has started, start a timer.""" |
374 | @@ -159,8 +170,12 @@ | |||
375 | 159 | for line in data.split("\n"): | 170 | for line in data.split("\n"): |
376 | 160 | if line.startswith(TUNNEL_PORT_LABEL): | 171 | if line.startswith(TUNNEL_PORT_LABEL): |
377 | 161 | _header, port = line.split(":", 1) | 172 | _header, port = line.split(":", 1) |
383 | 162 | port = int(port.strip()) | 173 | self.port = int(port.strip()) |
384 | 163 | logger.info("Tunnel process listening on port %r.", port) | 174 | if line.startswith(TUNNEL_COOKIE_LABEL): |
385 | 164 | client = TunnelClient(LOCALHOST, port) | 175 | _header, cookie = line.split(":", 1) |
386 | 165 | self.client_d.callback(client) | 176 | self.cookie = cookie.strip() |
387 | 166 | break | 177 | |
388 | 178 | if self.port and self.cookie: | ||
389 | 179 | logger.info("Tunnel process listening on port %r.", self.port) | ||
390 | 180 | client = TunnelClient(LOCALHOST, self.port, self.cookie) | ||
391 | 181 | self.client_d.callback(client) | ||
392 | 167 | 182 | ||
393 | === modified file 'ubuntuone/proxy/tunnel_server.py' | |||
394 | --- ubuntuone/proxy/tunnel_server.py 2012-03-16 13:44:20 +0000 | |||
395 | +++ ubuntuone/proxy/tunnel_server.py 2012-03-16 13:44:20 +0000 | |||
396 | @@ -30,6 +30,7 @@ | |||
397 | 30 | """ | 30 | """ |
398 | 31 | 31 | ||
399 | 32 | import sys | 32 | import sys |
400 | 33 | import uuid | ||
401 | 33 | 34 | ||
402 | 34 | from PyQt4.QtCore import QCoreApplication, QTimer | 35 | from PyQt4.QtCore import QCoreApplication, QTimer |
403 | 35 | from PyQt4.QtNetwork import ( | 36 | from PyQt4.QtNetwork import ( |
404 | @@ -46,7 +47,13 @@ | |||
405 | 46 | 47 | ||
406 | 47 | from ubuntu_sso.keyring import Keyring | 48 | from ubuntu_sso.keyring import Keyring |
407 | 48 | from ubuntu_sso.utils.webclient import gsettings | 49 | from ubuntu_sso.utils.webclient import gsettings |
409 | 49 | from ubuntuone.proxy.common import BaseTunnelProtocol, CRLF, TUNNEL_PORT_LABEL | 50 | from ubuntuone.proxy.common import ( |
410 | 51 | BaseTunnelProtocol, | ||
411 | 52 | CRLF, | ||
412 | 53 | TUNNEL_COOKIE_HEADER, | ||
413 | 54 | TUNNEL_COOKIE_LABEL, | ||
414 | 55 | TUNNEL_PORT_LABEL, | ||
415 | 56 | ) | ||
416 | 50 | from ubuntuone.proxy.logger import logger | 57 | from ubuntuone.proxy.logger import logger |
417 | 51 | 58 | ||
418 | 52 | DEFAULT_CODE = 500 | 59 | DEFAULT_CODE = 500 |
419 | @@ -219,10 +226,17 @@ | |||
420 | 219 | except ValueError: | 226 | except ValueError: |
421 | 220 | self.error_response(400, "Bad request") | 227 | self.error_response(400, "Bad request") |
422 | 221 | 228 | ||
423 | 229 | def verify_cookie(self): | ||
424 | 230 | """Fail if the cookie is wrong or missing.""" | ||
425 | 231 | cookie_received = dict(self.received_headers).get(TUNNEL_COOKIE_HEADER) | ||
426 | 232 | if cookie_received != self.transport.cookie: | ||
427 | 233 | raise ConnectionError(418, "Please see RFC 2324") | ||
428 | 234 | |||
429 | 222 | @defer.inlineCallbacks | 235 | @defer.inlineCallbacks |
430 | 223 | def headers_done(self): | 236 | def headers_done(self): |
431 | 224 | """An empty line was received, start connecting and switch mode.""" | 237 | """An empty line was received, start connecting and switch mode.""" |
432 | 225 | try: | 238 | try: |
433 | 239 | self.verify_cookie() | ||
434 | 226 | try: | 240 | try: |
435 | 227 | logger.info("Connecting once") | 241 | logger.info("Connecting once") |
436 | 228 | self.client = self.client_class(self) | 242 | self.client = self.client_class(self) |
437 | @@ -267,8 +281,9 @@ | |||
438 | 267 | 281 | ||
439 | 268 | implements(interfaces.ITransport) | 282 | implements(interfaces.ITransport) |
440 | 269 | 283 | ||
442 | 270 | def __init__(self, local_socket): | 284 | def __init__(self, local_socket, cookie): |
443 | 271 | """Initialize this Tunnel instance.""" | 285 | """Initialize this Tunnel instance.""" |
444 | 286 | self.cookie = cookie | ||
445 | 272 | self.disconnecting = False | 287 | self.disconnecting = False |
446 | 273 | self.local_socket = local_socket | 288 | self.local_socket = local_socket |
447 | 274 | self.protocol = ServerTunnelProtocol(RemoteSocket) | 289 | self.protocol = ServerTunnelProtocol(RemoteSocket) |
448 | @@ -300,9 +315,10 @@ | |||
449 | 300 | class TunnelServer(object): | 315 | class TunnelServer(object): |
450 | 301 | """A server for tunnel instances.""" | 316 | """A server for tunnel instances.""" |
451 | 302 | 317 | ||
453 | 303 | def __init__(self): | 318 | def __init__(self, cookie): |
454 | 304 | """Initialize this tunnel instance.""" | 319 | """Initialize this tunnel instance.""" |
455 | 305 | self.tunnels = [] | 320 | self.tunnels = [] |
456 | 321 | self.cookie = cookie | ||
457 | 306 | self.server = QTcpServer(QCoreApplication.instance()) | 322 | self.server = QTcpServer(QCoreApplication.instance()) |
458 | 307 | self.server.newConnection.connect(self.new_connection) | 323 | self.server.newConnection.connect(self.new_connection) |
459 | 308 | self.server.listen(QHostAddress.LocalHost, 0) | 324 | self.server.listen(QHostAddress.LocalHost, 0) |
460 | @@ -312,7 +328,7 @@ | |||
461 | 312 | """On a new connection create a new tunnel instance.""" | 328 | """On a new connection create a new tunnel instance.""" |
462 | 313 | logger.info("New connection made") | 329 | logger.info("New connection made") |
463 | 314 | local_socket = self.server.nextPendingConnection() | 330 | local_socket = self.server.nextPendingConnection() |
465 | 315 | tunnel = Tunnel(local_socket) | 331 | tunnel = Tunnel(local_socket, self.cookie) |
466 | 316 | self.tunnels.append(tunnel) | 332 | self.tunnels.append(tunnel) |
467 | 317 | 333 | ||
468 | 318 | def shutdown(self): | 334 | def shutdown(self): |
469 | @@ -352,7 +368,9 @@ | |||
470 | 352 | from dbus.mainloop.qt import DBusQtMainLoop | 368 | from dbus.mainloop.qt import DBusQtMainLoop |
471 | 353 | DBusQtMainLoop(set_as_default=True) | 369 | DBusQtMainLoop(set_as_default=True) |
472 | 354 | app = QCoreApplication(argv) | 370 | app = QCoreApplication(argv) |
475 | 355 | tunnel_server = TunnelServer() | 371 | cookie = str(uuid.uuid4()) |
476 | 356 | sys.stdout.write("%s: %d\n" % (TUNNEL_PORT_LABEL, tunnel_server.port)) | 372 | tunnel_server = TunnelServer(cookie) |
477 | 373 | sys.stdout.write("%s: %d\n" % (TUNNEL_PORT_LABEL, tunnel_server.port) + | ||
478 | 374 | "%s: %s\n" % (TUNNEL_COOKIE_LABEL, cookie)) | ||
479 | 357 | sys.stdout.flush() | 375 | sys.stdout.flush() |
480 | 358 | app.exec_() | 376 | app.exec_() |
+1