Merge lp:~facundo/ubuntuone-storage-protocol/simple_auth into lp:ubuntuone-storage-protocol
- simple_auth
- Merge into trunk
Proposed by
Facundo Batista
Status: | Superseded |
---|---|
Proposed branch: | lp:~facundo/ubuntuone-storage-protocol/simple_auth |
Merge into: | lp:ubuntuone-storage-protocol |
Diff against target: |
402 lines (+40/-187) 4 files modified
tests/test_client.py (+6/-121) tests/test_context.py (+7/-2) ubuntuone/storageprotocol/client.py (+17/-60) ubuntuone/storageprotocol/context.py (+10/-4) |
To merge this branch: | bzr merge lp:~facundo/ubuntuone-storage-protocol/simple_auth |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Review via email: mp+259407@code.launchpad.net |
This proposal has been superseded by a proposal from 2015-05-20.
Commit message
Use simple authentication against the server.
Description of the change
Use simple authentication against the server.
To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) : | # |
review:
Approve
Unmerged revisions
- 165. By Facundo Batista
-
Use simple authentication against the server.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'tests/test_client.py' |
2 | --- tests/test_client.py 2013-02-06 22:06:03 +0000 |
3 | +++ tests/test_client.py 2015-05-18 17:24:40 +0000 |
4 | @@ -1,9 +1,6 @@ |
5 | # -*- coding: utf-8 -*- |
6 | # |
7 | -# Author: Natalia B. Bidart <natalia.bidart@canonical.com> |
8 | -# Author: Alejandro J. Cura <alecu@canonical.com> |
9 | -# |
10 | -# Copyright (C) 2009-2012 Canonical Ltd. |
11 | +# Copyright (C) 2009-2015 Canonical Ltd. |
12 | # |
13 | # This program is free software: you can redistribute it and/or modify it |
14 | # under the terms of the GNU Affero General Public License version 3, |
15 | @@ -34,15 +31,12 @@ |
16 | import StringIO |
17 | import os |
18 | import sys |
19 | -import time |
20 | import unittest |
21 | import uuid |
22 | |
23 | -from urlparse import urlparse, parse_qsl |
24 | - |
25 | from twisted.application import internet, service |
26 | from twisted.internet import defer |
27 | -from twisted.internet.defer import Deferred, inlineCallbacks |
28 | +from twisted.internet.defer import Deferred |
29 | from twisted.trial.unittest import TestCase as TwistedTestCase |
30 | from twisted.web import server, resource |
31 | |
32 | @@ -50,12 +44,10 @@ |
33 | from ubuntuone.storageprotocol.client import ( |
34 | StorageClient, CreateUDF, ListVolumes, DeleteVolume, GetDelta, Unlink, |
35 | Authenticate, MakeFile, MakeDir, PutContent, Move, BytesMessageProducer, |
36 | - TwistedTimestampChecker, tx_timestamp_checker, |
37 | ) |
38 | |
39 | from ubuntuone.storageprotocol import volumes |
40 | from tests import test_delta_info |
41 | -from oauthlib.oauth1 import Client |
42 | |
43 | # let's not get picky about aatributes outside __init__ in tests |
44 | # pylint: disable=W0201 |
45 | @@ -797,46 +789,6 @@ |
46 | self.assertEqual(len(msg.auth_parameters), 1) |
47 | self.assertEqual(len(msg.metadata), 0) |
48 | |
49 | - def test_oauth_authenticate_uses_server_timestamp(self): |
50 | - """The oauth authentication uses the server timestamp.""" |
51 | - timestamps = [] |
52 | - |
53 | - original_sign = Client.sign |
54 | - |
55 | - def fake_sign(self, *args, **kwargs): |
56 | - """A fake Client.sign().""" |
57 | - url, headers, body = original_sign(self, *args, **kwargs) |
58 | - timestamps.extend( |
59 | - value for name, value in parse_qsl(urlparse(url).query) |
60 | - if name == 'oauth_timestamp') |
61 | - return url, headers, body |
62 | - |
63 | - fake_timestamp = '801' |
64 | - timestamp_d = Deferred() |
65 | - self.patch(tx_timestamp_checker, "get_faithful_time", |
66 | - lambda: timestamp_d) |
67 | - |
68 | - self.patch(Client, 'sign', fake_sign) |
69 | - protocol = FakedProtocol() |
70 | - # For backward compatibility of the API, oauth_authenticate() takes a |
71 | - # consumer object and a token object. Both of those must have .key |
72 | - # and .secret attributes. In modern OAuth1 (i.e. RFC 5849) |
73 | - # terminology, the consumer is really the OAuth client, and the token |
74 | - # is really the OAuth resource owner. |
75 | - |
76 | - class OAuthClient: |
77 | - key = 'consumer_key' |
78 | - secret = 'consumer_secret' |
79 | - |
80 | - class OAuthResourceOwner: |
81 | - key = 'token' |
82 | - secret = 'token_secret' |
83 | - protocol.oauth_authenticate(OAuthResourceOwner, OAuthClient) |
84 | - self.assertEqual(len(timestamps), 0) |
85 | - timestamp_d.callback(fake_timestamp) |
86 | - self.assertEqual(len(timestamps), 1) |
87 | - self.assertEqual(timestamps[0], fake_timestamp) |
88 | - |
89 | |
90 | class RootResource(resource.Resource): |
91 | """A root resource that logs the number of calls.""" |
92 | @@ -878,73 +830,6 @@ |
93 | self.service_collection.stopService() |
94 | |
95 | |
96 | -class TimestampCheckerTestCase(TwistedTestCase): |
97 | - """Tests for the timestamp checker.""" |
98 | - |
99 | - @defer.inlineCallbacks |
100 | - def setUp(self): |
101 | - yield super(TimestampCheckerTestCase, self).setUp() |
102 | - self.ws = MockWebServer() |
103 | - self.addCleanup(self.ws.stop) |
104 | - self.patch(TwistedTimestampChecker, "SERVER_URL", self.ws.get_url()) |
105 | - |
106 | - @inlineCallbacks |
107 | - def test_returned_value_is_int(self): |
108 | - """The returned value is an integer.""" |
109 | - checker = TwistedTimestampChecker() |
110 | - t = yield checker.get_faithful_time() |
111 | - self.assertEqual(type(t), int) |
112 | - |
113 | - @inlineCallbacks |
114 | - def test_first_call_does_head(self): |
115 | - """The first call gets the clock from our web.""" |
116 | - checker = TwistedTimestampChecker() |
117 | - yield checker.get_faithful_time() |
118 | - self.assertEqual(self.ws.root.count, 1) |
119 | - |
120 | - @inlineCallbacks |
121 | - def test_second_call_is_cached(self): |
122 | - """For the second call, the time is cached.""" |
123 | - checker = TwistedTimestampChecker() |
124 | - yield checker.get_faithful_time() |
125 | - yield checker.get_faithful_time() |
126 | - self.assertEqual(self.ws.root.count, 1) |
127 | - |
128 | - @inlineCallbacks |
129 | - def test_after_timeout_cache_expires(self): |
130 | - """After some time, the cache expires.""" |
131 | - fake_timestamp = 1 |
132 | - self.patch(time, "time", lambda: fake_timestamp) |
133 | - checker = TwistedTimestampChecker() |
134 | - yield checker.get_faithful_time() |
135 | - fake_timestamp += TwistedTimestampChecker.CHECKING_INTERVAL |
136 | - yield checker.get_faithful_time() |
137 | - self.assertEqual(self.ws.root.count, 2) |
138 | - |
139 | - @inlineCallbacks |
140 | - def test_server_error_means_skew_not_updated(self): |
141 | - """When server can't be reached, the skew is not updated.""" |
142 | - fake_timestamp = 1 |
143 | - self.patch(time, "time", lambda: fake_timestamp) |
144 | - checker = TwistedTimestampChecker() |
145 | - failing_get_server_time = lambda: defer.fail(FakedError()) |
146 | - self.patch(checker, "get_server_time", failing_get_server_time) |
147 | - yield checker.get_faithful_time() |
148 | - self.assertEqual(checker.skew, 0) |
149 | - self.assertEqual(checker.next_check, |
150 | - fake_timestamp + TwistedTimestampChecker.ERROR_INTERVAL) |
151 | - |
152 | - @inlineCallbacks |
153 | - def test_server_date_sends_nocache_headers(self): |
154 | - """Getting the server date sends the no-cache headers.""" |
155 | - checker = TwistedTimestampChecker() |
156 | - yield checker.get_server_date_header(self.ws.get_url()) |
157 | - assert len(self.ws.root.request_headers) == 1 |
158 | - headers = self.ws.root.request_headers[0] |
159 | - result = headers.getRawHeaders("Cache-Control") |
160 | - self.assertEqual(result, ["no-cache"]) |
161 | - |
162 | - |
163 | class TestGenerationInRequests(RequestTestCase): |
164 | """Base class for testing that actions that change the volume will |
165 | have a new_generation attribute set.""" |
166 | @@ -992,8 +877,8 @@ |
167 | |
168 | def build_request(self): |
169 | """Creates the request object.""" |
170 | - return self.request_class(FakedProtocol(), None, None, None, None, |
171 | - None, None, None, None) |
172 | + return self.request_class( |
173 | + FakedProtocol(), None, None, None, None, None, None, None, None) |
174 | |
175 | def build_message(self): |
176 | """Creates the ending message for the request.""" |
177 | @@ -1040,8 +925,8 @@ |
178 | def test_bytesmessageproducer_maxpayloadsize(self): |
179 | """The producer uses the payload size from the request.""" |
180 | # set up the PutContent |
181 | - pc = self.request_class(FakedProtocol(), None, None, None, None, |
182 | - None, None, None, None) |
183 | + pc = self.request_class( |
184 | + FakedProtocol(), None, None, None, None, None, None, None, None) |
185 | assert 12345 != pc.max_payload_size |
186 | pc.max_payload_size = 12345 |
187 | |
188 | |
189 | === modified file 'tests/test_context.py' |
190 | --- tests/test_context.py 2014-04-14 22:21:04 +0000 |
191 | +++ tests/test_context.py 2015-05-18 17:24:40 +0000 |
192 | @@ -1,6 +1,6 @@ |
193 | # tests.test_context - test ssl context creation |
194 | # |
195 | -# Copyright 2012 Canonical Ltd. |
196 | +# Copyright 2012-2015 Canonical Ltd. |
197 | # |
198 | # This program is free software: you can redistribute it and/or modify it |
199 | # under the terms of the GNU Affero General Public License version 3, |
200 | @@ -35,7 +35,12 @@ |
201 | from twisted.web import client, resource, server |
202 | |
203 | from ubuntuone.storageprotocol import context |
204 | -from ubuntu_sso.utils import get_cert_dir |
205 | + |
206 | +try: |
207 | + from ubuntu_sso.utils import get_cert_dir |
208 | +except ImportError: |
209 | + # old system, let's support Precise |
210 | + get_cert_dir = lambda: '/etc/ssl/certs' |
211 | |
212 | |
213 | class FakeCerts(object): |
214 | |
215 | === modified file 'ubuntuone/storageprotocol/client.py' |
216 | --- ubuntuone/storageprotocol/client.py 2013-02-25 15:21:30 +0000 |
217 | +++ ubuntuone/storageprotocol/client.py 2015-05-18 17:24:40 +0000 |
218 | @@ -6,7 +6,7 @@ |
219 | # Author: Facundo Batista <facundo@canonical.com> |
220 | # Author: Alejandro J. Cura <alecu@canonical.com> |
221 | # |
222 | -# Copyright 2009-2012 Canonical Ltd. |
223 | +# Copyright 2009-2015 Canonical Ltd. |
224 | # |
225 | # This program is free software: you can redistribute it and/or modify it |
226 | # under the terms of the GNU Affero General Public License version 3, |
227 | @@ -39,42 +39,22 @@ |
228 | |
229 | from functools import partial |
230 | from itertools import chain |
231 | -from urlparse import urlparse, parse_qsl |
232 | -from oauthlib.oauth1 import Client, SIGNATURE_PLAINTEXT, SIGNATURE_TYPE_QUERY |
233 | |
234 | from twisted.internet.protocol import ClientFactory |
235 | from twisted.internet import reactor, defer |
236 | from twisted.python import log |
237 | -from twisted.web import client |
238 | -from twisted.web.http_headers import Headers |
239 | |
240 | from ubuntuone.storageprotocol import ( |
241 | delta, |
242 | protocol_pb2, |
243 | request, |
244 | sharersp, |
245 | - utils, |
246 | volumes, |
247 | ) |
248 | |
249 | log_debug = partial(log.msg, loglevel=logging.DEBUG) |
250 | |
251 | |
252 | -class TwistedTimestampChecker(utils.BaseTimestampChecker): |
253 | - """A timestamp that's regularly checked with a server.""" |
254 | - |
255 | - @defer.inlineCallbacks |
256 | - def get_server_date_header(self, server_url): |
257 | - """Get the server date using twisted webclient.""" |
258 | - agent = client.Agent(reactor) |
259 | - headers = Headers({'Cache-Control': ['no-cache']}) |
260 | - response = yield agent.request("HEAD", server_url, headers) |
261 | - defer.returnValue(response.headers.getRawHeaders("Date")[0]) |
262 | - |
263 | - |
264 | -tx_timestamp_checker = TwistedTimestampChecker() |
265 | - |
266 | - |
267 | class StorageClient(request.RequestHandler): |
268 | """A Basic Storage Protocol client.""" |
269 | |
270 | @@ -140,38 +120,15 @@ |
271 | p.start() |
272 | return p.deferred |
273 | |
274 | - @defer.inlineCallbacks |
275 | - def oauth_authenticate(self, consumer, token, metadata=None): |
276 | - """Authenticate to a server using the OAuth provider. |
277 | - |
278 | - @param consumer: the OAuth consumer (a.k.a. client in RFC 5849) to |
279 | - authenticate with. |
280 | - @type consumer: object having both a `.key` and `.secret` attribute. |
281 | - @param token: a previously acquired OAuth access token (a.k.a. |
282 | - resource owner in RFC 5849). |
283 | - @type token: object having both a `.key` and `.secret` attribute. |
284 | - @param metadata: key/values to send as metadata |
285 | - |
286 | - Return a deferred that will get called with the request |
287 | - object when completed. |
288 | - |
289 | - """ |
290 | - timestamp = yield tx_timestamp_checker.get_faithful_time() |
291 | - client = Client(consumer.key, consumer.secret, |
292 | - token.key, token.secret, |
293 | - signature_method=SIGNATURE_PLAINTEXT, |
294 | - signature_type=SIGNATURE_TYPE_QUERY, |
295 | - timestamp=timestamp) |
296 | - url, headers, body = client.sign('http://server') |
297 | - # Parse out the authentication parameters from the query string. |
298 | - auth_parameters = dict( |
299 | - (name, value) for name, value in |
300 | - parse_qsl(urlparse(url).query) |
301 | - if name.startswith('oauth_')) |
302 | + def simple_authenticate(self, username, password, metadata=None): |
303 | + """Authenticate to a server using the username and password.""" |
304 | + auth_parameters = { |
305 | + 'username': username, |
306 | + 'password': password, |
307 | + } |
308 | p = Authenticate(self, auth_parameters, metadata=metadata) |
309 | p.start() |
310 | - result = yield p.deferred |
311 | - defer.returnValue(result) |
312 | + return p.deferred |
313 | |
314 | def handle_ROOT(self, message): |
315 | """Handle incoming ROOT message. |
316 | @@ -1756,10 +1713,10 @@ |
317 | def checkReadBandwidth(self): |
318 | """Check if we've passed bandwidth limits.""" |
319 | limit_check = self.valid_limit(self.readLimit) and \ |
320 | - self.readLimit is not None and \ |
321 | - self.readThisSecond >= self.readLimit |
322 | + self.readLimit is not None and \ |
323 | + self.readThisSecond >= self.readLimit |
324 | should_check = self.throttling_enabled and limit_check and \ |
325 | - self.unthrottleReadsID is None |
326 | + self.unthrottleReadsID is None |
327 | if should_check: |
328 | self.throttleReads() |
329 | throttle_time = self._get_throttle_time(self.readThisSecond, |
330 | @@ -1771,14 +1728,14 @@ |
331 | def checkWriteBandwidth(self): |
332 | """Check if we've passed bandwidth limits.""" |
333 | limit_check = self.valid_limit(self.writeLimit) and \ |
334 | - self.writeLimit is not None and \ |
335 | - self.writtenThisSecond >= self.writeLimit |
336 | + self.writeLimit is not None and \ |
337 | + self.writtenThisSecond >= self.writeLimit |
338 | should_check = self.throttling_enabled and limit_check and \ |
339 | - self.unthrottleWritesID is None |
340 | + self.unthrottleWritesID is None |
341 | if should_check: |
342 | self.throttleWrites() |
343 | throttle_time = self._get_throttle_time(self.writtenThisSecond, |
344 | - self.writeLimit) |
345 | + self.writeLimit) |
346 | log_debug("pause writes for: %s", str(throttle_time)) |
347 | self.unthrottleWritesID = self.maybeCallLater( |
348 | 'unthrottleWritesID', throttle_time, self.unthrottleWrites) |
349 | @@ -1787,8 +1744,8 @@ |
350 | """Reset the counter named with 'name' every 1 second.""" |
351 | # check the bandwidth limits |
352 | self.readThisSecond = 0 |
353 | - self.resetReadThisSecondID = self.callLater(1, |
354 | - self._resetReadThisSecond) |
355 | + self.resetReadThisSecondID = self.callLater( |
356 | + 1, self._resetReadThisSecond) |
357 | |
358 | def _resetWrittenThisSecond(self): |
359 | """Reset the counter named with 'name' every 1 second.""" |
360 | |
361 | === modified file 'ubuntuone/storageprotocol/context.py' |
362 | --- ubuntuone/storageprotocol/context.py 2014-04-14 22:21:04 +0000 |
363 | +++ ubuntuone/storageprotocol/context.py 2015-05-18 17:24:40 +0000 |
364 | @@ -1,6 +1,6 @@ |
365 | # ubuntuone.storageprotocol.context - ssl context creation |
366 | # |
367 | -# Copyright 2009-2012 Canonical Ltd. |
368 | +# Copyright 2009-2015 Canonical Ltd. |
369 | # |
370 | # This program is free software: you can redistribute it and/or modify it |
371 | # under the terms of the GNU Affero General Public License version 3, |
372 | @@ -35,7 +35,11 @@ |
373 | from twisted.internet import error, ssl |
374 | from twisted.python import log |
375 | |
376 | -from ubuntu_sso.utils import get_cert_dir |
377 | +try: |
378 | + from ubuntu_sso.utils import get_cert_dir |
379 | +except ImportError: |
380 | + # old system, let's support Precise |
381 | + get_cert_dir = lambda: '/etc/ssl/certs' |
382 | |
383 | |
384 | class HostnameVerifyContextFactory(ssl.CertificateOptions): |
385 | @@ -52,7 +56,8 @@ |
386 | # No extra checks because U1 certs have the right commonName |
387 | if self.expected_hostname != cert.get_subject().commonName: |
388 | log.err("Host name does not match certificate. " |
389 | - "Expected %s but got %s." % (self.expected_hostname, |
390 | + "Expected %s but got %s." % ( |
391 | + self.expected_hostname, |
392 | cert.get_subject().commonName)) |
393 | return False |
394 | return preverifyOK |
395 | @@ -95,5 +100,6 @@ |
396 | raise error.CertificateError( |
397 | 'No hostname specified. Unable to verify SSL certificate.') |
398 | ctx = HostnameVerifyContextFactory(hostname, verify=True, |
399 | - caCerts=get_certificates(), method=SSL.SSLv23_METHOD) |
400 | + caCerts=get_certificates(), |
401 | + method=SSL.SSLv23_METHOD) |
402 | return ctx |