Merge lp:~alecu/ubuntu-sso-client/timestamp-autofix-1-2 into lp:ubuntu-sso-client/stable-1-2
- timestamp-autofix-1-2
- Merge into stable-1-2
Proposed by
Alejandro J. Cura
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Natalia Bidart | ||||||||
Approved revision: | 692 | ||||||||
Merged at revision: | 689 | ||||||||
Proposed branch: | lp:~alecu/ubuntu-sso-client/timestamp-autofix-1-2 | ||||||||
Merge into: | lp:ubuntu-sso-client/stable-1-2 | ||||||||
Diff against target: |
484 lines (+318/-20) 6 files modified
ubuntu_sso/account.py (+33/-7) ubuntu_sso/credentials.py (+10/-3) ubuntu_sso/tests/test_account.py (+60/-8) ubuntu_sso/tests/test_credentials.py (+4/-1) ubuntu_sso/utils/__init__.py (+63/-1) ubuntu_sso/utils/tests/test_oauth_headers.py (+148/-0) |
||||||||
To merge this branch: | bzr merge lp:~alecu/ubuntu-sso-client/timestamp-autofix-1-2 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Natalia Bidart (community) | Approve | ||
Diego Sarmentero (community) | Abstain | ||
Review via email: mp+82685@code.launchpad.net |
This proposal supersedes a proposal from 2011-11-18.
To post a comment you must log in.
Revision history for this message
Diego Sarmentero (diegosarmentero) : | # |
review:
Abstain
- 692. By Alejandro J. Cura
-
fix year in file headers
Revision history for this message
Natalia Bidart (nataliabidart) wrote : | # |
Tested locally and IRL. It works great, and all tests are green.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'ubuntu_sso/account.py' | |||
2 | --- ubuntu_sso/account.py 2011-03-16 01:36:07 +0000 | |||
3 | +++ ubuntu_sso/account.py 2011-11-23 19:52:23 +0000 | |||
4 | @@ -1,8 +1,9 @@ | |||
5 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
6 | 2 | 2 | ||
7 | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> |
8 | 4 | # Author: Alejandro J. Cura <alecu@canonical.com> | ||
9 | 4 | # | 5 | # |
11 | 5 | # Copyright 2010 Canonical Ltd. | 6 | # Copyright 2010, 2011 Canonical Ltd. |
12 | 6 | # | 7 | # |
13 | 7 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
14 | 8 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
15 | @@ -31,6 +32,7 @@ | |||
16 | 31 | from oauth import oauth | 32 | from oauth import oauth |
17 | 32 | 33 | ||
18 | 33 | from ubuntu_sso.logger import setup_logging | 34 | from ubuntu_sso.logger import setup_logging |
19 | 35 | from ubuntu_sso.utils import timestamp_checker | ||
20 | 34 | 36 | ||
21 | 35 | 37 | ||
22 | 36 | logger = setup_logging("ubuntu_sso.account") | 38 | logger = setup_logging("ubuntu_sso.account") |
23 | @@ -39,6 +41,27 @@ | |||
24 | 39 | SSO_STATUS_ERROR = 'error' | 41 | SSO_STATUS_ERROR = 'error' |
25 | 40 | 42 | ||
26 | 41 | 43 | ||
27 | 44 | class TimestampedAuthorizer(OAuthAuthorizer): | ||
28 | 45 | """Includes a custom timestamp on OAuth signatures.""" | ||
29 | 46 | |||
30 | 47 | def __init__(self, get_timestamp, *args, **kwargs): | ||
31 | 48 | """Store the get_timestamp method, and move on.""" | ||
32 | 49 | OAuthAuthorizer.__init__(self, *args, **kwargs) | ||
33 | 50 | self.get_timestamp = get_timestamp | ||
34 | 51 | |||
35 | 52 | # pylint: disable=C0103,E1101 | ||
36 | 53 | def authorizeRequest(self, absolute_uri, method, body, headers): | ||
37 | 54 | """Override authorizeRequest including the timestamp.""" | ||
38 | 55 | parameters = {"oauth_timestamp": self.get_timestamp()} | ||
39 | 56 | oauth_request = oauth.OAuthRequest.from_consumer_and_token( | ||
40 | 57 | self.consumer, self.access_token, http_url=absolute_uri, | ||
41 | 58 | parameters=parameters) | ||
42 | 59 | oauth_request.sign_request( | ||
43 | 60 | oauth.OAuthSignatureMethod_PLAINTEXT(), | ||
44 | 61 | self.consumer, self.access_token) | ||
45 | 62 | headers.update(oauth_request.to_header(self.oauth_realm)) | ||
46 | 63 | |||
47 | 64 | |||
48 | 42 | class InvalidEmailError(Exception): | 65 | class InvalidEmailError(Exception): |
49 | 43 | """The email is not valid.""" | 66 | """The email is not valid.""" |
50 | 44 | 67 | ||
51 | @@ -181,9 +204,11 @@ | |||
52 | 181 | if sso_service is None: | 204 | if sso_service is None: |
53 | 182 | oauth_token = oauth.OAuthToken(token['token'], | 205 | oauth_token = oauth.OAuthToken(token['token'], |
54 | 183 | token['token_secret']) | 206 | token['token_secret']) |
58 | 184 | authorizer = OAuthAuthorizer(token['consumer_key'], | 207 | authorizer = TimestampedAuthorizer( |
59 | 185 | token['consumer_secret'], | 208 | timestamp_checker.get_faithful_time, |
60 | 186 | oauth_token) | 209 | token['consumer_key'], |
61 | 210 | token['consumer_secret'], | ||
62 | 211 | oauth_token) | ||
63 | 187 | sso_service = self.sso_service_class(authorizer, self.service_url) | 212 | sso_service = self.sso_service_class(authorizer, self.service_url) |
64 | 188 | 213 | ||
65 | 189 | me_info = sso_service.accounts.me() | 214 | me_info = sso_service.accounts.me() |
66 | @@ -203,9 +228,10 @@ | |||
67 | 203 | token_name=token_name) | 228 | token_name=token_name) |
68 | 204 | 229 | ||
69 | 205 | oauth_token = oauth.OAuthToken(token['token'], token['token_secret']) | 230 | oauth_token = oauth.OAuthToken(token['token'], token['token_secret']) |
73 | 206 | authorizer = OAuthAuthorizer(token['consumer_key'], | 231 | authorizer = TimestampedAuthorizer(timestamp_checker.get_faithful_time, |
74 | 207 | token['consumer_secret'], | 232 | token['consumer_key'], |
75 | 208 | oauth_token) | 233 | token['consumer_secret'], |
76 | 234 | oauth_token) | ||
77 | 209 | sso_service = self.sso_service_class(authorizer, self.service_url) | 235 | sso_service = self.sso_service_class(authorizer, self.service_url) |
78 | 210 | result = sso_service.accounts.validate_email(email_token=email_token) | 236 | result = sso_service.accounts.validate_email(email_token=email_token) |
79 | 211 | logger.info('validate_email: email: %r result: %r', email, result) | 237 | logger.info('validate_email: email: %r result: %r', email, result) |
80 | 212 | 238 | ||
81 | === modified file 'ubuntu_sso/credentials.py' | |||
82 | --- ubuntu_sso/credentials.py 2011-01-02 03:34:23 +0000 | |||
83 | +++ ubuntu_sso/credentials.py 2011-11-23 19:52:23 +0000 | |||
84 | @@ -3,7 +3,7 @@ | |||
85 | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> |
86 | 4 | # Author: Alejandro J. Cura <alecu@canonical.com> | 4 | # Author: Alejandro J. Cura <alecu@canonical.com> |
87 | 5 | # | 5 | # |
89 | 6 | # Copyright 2010 Canonical Ltd. | 6 | # Copyright 2010, 2011 Canonical Ltd. |
90 | 7 | # | 7 | # |
91 | 8 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
92 | 9 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
93 | @@ -46,7 +46,7 @@ | |||
94 | 46 | from oauth import oauth | 46 | from oauth import oauth |
95 | 47 | from twisted.internet.defer import inlineCallbacks, returnValue | 47 | from twisted.internet.defer import inlineCallbacks, returnValue |
96 | 48 | 48 | ||
98 | 49 | from ubuntu_sso import NO_OP | 49 | from ubuntu_sso import NO_OP, utils |
99 | 50 | from ubuntu_sso.keyring import Keyring | 50 | from ubuntu_sso.keyring import Keyring |
100 | 51 | from ubuntu_sso.logger import setup_logging | 51 | from ubuntu_sso.logger import setup_logging |
101 | 52 | 52 | ||
102 | @@ -217,6 +217,10 @@ | |||
103 | 217 | logger.warning('Login/registration was denied to app %r', app_name) | 217 | logger.warning('Login/registration was denied to app %r', app_name) |
104 | 218 | self.denial_cb(app_name) | 218 | self.denial_cb(app_name) |
105 | 219 | 219 | ||
106 | 220 | def _get_timestamp(self): | ||
107 | 221 | """Get the timestamp calculated from the server.""" | ||
108 | 222 | return utils.timestamp_checker.get_faithful_time() | ||
109 | 223 | |||
110 | 220 | @handle_failures(msg='Problem opening the ping_url') | 224 | @handle_failures(msg='Problem opening the ping_url') |
111 | 221 | @inlineCallbacks | 225 | @inlineCallbacks |
112 | 222 | def _ping_url(self, app_name, email, credentials): | 226 | def _ping_url(self, app_name, email, credentials): |
113 | @@ -233,9 +237,12 @@ | |||
114 | 233 | credentials['consumer_secret']) | 237 | credentials['consumer_secret']) |
115 | 234 | token = oauth.OAuthToken(credentials['token'], | 238 | token = oauth.OAuthToken(credentials['token'], |
116 | 235 | credentials['token_secret']) | 239 | credentials['token_secret']) |
117 | 240 | timestamp = self._get_timestamp() | ||
118 | 241 | parameters = {"oauth_timestamp": timestamp} | ||
119 | 236 | get_request = oauth.OAuthRequest.from_consumer_and_token | 242 | get_request = oauth.OAuthRequest.from_consumer_and_token |
120 | 237 | oauth_req = get_request(oauth_consumer=consumer, token=token, | 243 | oauth_req = get_request(oauth_consumer=consumer, token=token, |
122 | 238 | http_method='GET', http_url=url) | 244 | http_method='GET', http_url=url, |
123 | 245 | parameters=parameters) | ||
124 | 239 | oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), | 246 | oauth_req.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), |
125 | 240 | consumer, token) | 247 | consumer, token) |
126 | 241 | request = urllib2.Request(url, headers=oauth_req.to_header()) | 248 | request = urllib2.Request(url, headers=oauth_req.to_header()) |
127 | 242 | 249 | ||
128 | === modified file 'ubuntu_sso/tests/test_account.py' | |||
129 | --- ubuntu_sso/tests/test_account.py 2011-03-16 01:36:07 +0000 | |||
130 | +++ ubuntu_sso/tests/test_account.py 2011-11-23 19:52:23 +0000 | |||
131 | @@ -1,8 +1,9 @@ | |||
132 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
133 | 2 | # | 2 | # |
134 | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> |
135 | 4 | # Author: Alejandro J. Cura <alecu@canonical.com> | ||
136 | 4 | # | 5 | # |
138 | 5 | # Copyright 2010 Canonical Ltd. | 6 | # Copyright 2010, 2011 Canonical Ltd. |
139 | 6 | # | 7 | # |
140 | 7 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
141 | 8 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
142 | @@ -23,15 +24,36 @@ | |||
143 | 23 | # pylint: disable=F0401 | 24 | # pylint: disable=F0401 |
144 | 24 | from lazr.restfulclient.errors import HTTPError | 25 | from lazr.restfulclient.errors import HTTPError |
145 | 25 | # pylint: enable=F0401 | 26 | # pylint: enable=F0401 |
146 | 27 | from oauth import oauth | ||
147 | 26 | from twisted.trial.unittest import TestCase | 28 | from twisted.trial.unittest import TestCase |
148 | 27 | 29 | ||
156 | 28 | from ubuntu_sso.account import (Account, AuthenticationError, EmailTokenError, | 30 | from ubuntu_sso.account import ( |
157 | 29 | InvalidEmailError, InvalidPasswordError, NewPasswordError, SERVICE_URL, | 31 | Account, |
158 | 30 | RegistrationError, ResetPasswordTokenError, | 32 | AuthenticationError, |
159 | 31 | SSO_STATUS_OK, SSO_STATUS_ERROR) | 33 | EmailTokenError, |
160 | 32 | from ubuntu_sso.tests import (APP_NAME, CAPTCHA_ID, CAPTCHA_PATH, | 34 | InvalidEmailError, |
161 | 33 | CAPTCHA_SOLUTION, EMAIL, EMAIL_TOKEN, NAME, PASSWORD, RESET_PASSWORD_TOKEN, | 35 | InvalidPasswordError, |
162 | 34 | TOKEN, TOKEN_NAME) | 36 | NewPasswordError, |
163 | 37 | SERVICE_URL, | ||
164 | 38 | RegistrationError, | ||
165 | 39 | ResetPasswordTokenError, | ||
166 | 40 | SSO_STATUS_OK, | ||
167 | 41 | SSO_STATUS_ERROR, | ||
168 | 42 | TimestampedAuthorizer, | ||
169 | 43 | ) | ||
170 | 44 | from ubuntu_sso.tests import ( | ||
171 | 45 | APP_NAME, | ||
172 | 46 | CAPTCHA_ID, | ||
173 | 47 | CAPTCHA_PATH, | ||
174 | 48 | CAPTCHA_SOLUTION, | ||
175 | 49 | EMAIL, | ||
176 | 50 | EMAIL_TOKEN, | ||
177 | 51 | NAME, | ||
178 | 52 | PASSWORD, | ||
179 | 53 | RESET_PASSWORD_TOKEN, | ||
180 | 54 | TOKEN, | ||
181 | 55 | TOKEN_NAME, | ||
182 | 56 | ) | ||
183 | 35 | 57 | ||
184 | 36 | 58 | ||
185 | 37 | CANT_RESET_PASSWORD_CONTENT = "CanNotResetPassowrdError: " \ | 59 | CANT_RESET_PASSWORD_CONTENT = "CanNotResetPassowrdError: " \ |
186 | @@ -142,6 +164,36 @@ | |||
187 | 142 | self.accounts = FakedAccounts() | 164 | self.accounts = FakedAccounts() |
188 | 143 | 165 | ||
189 | 144 | 166 | ||
190 | 167 | class TimestampedAuthorizerTestCase(TestCase): | ||
191 | 168 | """Test suite for the TimestampedAuthorizer.""" | ||
192 | 169 | |||
193 | 170 | def test_authorize_request_includes_timestamp(self): | ||
194 | 171 | """The authorizeRequest method includes the timestamp.""" | ||
195 | 172 | fromcandt_call = [] | ||
196 | 173 | fake_uri = "http://protocultura.net" | ||
197 | 174 | fake_timestamp = 1234 | ||
198 | 175 | get_fake_timestamp = lambda: fake_timestamp | ||
199 | 176 | original_oauthrequest = oauth.OAuthRequest | ||
200 | 177 | |||
201 | 178 | class FakeOAuthRequest(oauth.OAuthRequest): | ||
202 | 179 | """A Fake OAuthRequest class.""" | ||
203 | 180 | |||
204 | 181 | @staticmethod | ||
205 | 182 | def from_consumer_and_token(*args, **kwargs): | ||
206 | 183 | """A fake from_consumer_and_token.""" | ||
207 | 184 | fromcandt_call.append((args, kwargs)) | ||
208 | 185 | builder = original_oauthrequest.from_consumer_and_token | ||
209 | 186 | return builder(*args, **kwargs) | ||
210 | 187 | |||
211 | 188 | self.patch(oauth, "OAuthRequest", FakeOAuthRequest) | ||
212 | 189 | |||
213 | 190 | authorizer = TimestampedAuthorizer(get_fake_timestamp, "ubuntuone") | ||
214 | 191 | authorizer.authorizeRequest(fake_uri, "POST", None, {}) | ||
215 | 192 | call_kwargs = fromcandt_call[0][1] | ||
216 | 193 | parameters = call_kwargs["parameters"] | ||
217 | 194 | self.assertEqual(parameters["oauth_timestamp"], fake_timestamp) | ||
218 | 195 | |||
219 | 196 | |||
220 | 145 | class AccountTestCase(TestCase): | 197 | class AccountTestCase(TestCase): |
221 | 146 | """Test suite for the SSO login processor.""" | 198 | """Test suite for the SSO login processor.""" |
222 | 147 | 199 | ||
223 | 148 | 200 | ||
224 | === modified file 'ubuntu_sso/tests/test_credentials.py' | |||
225 | --- ubuntu_sso/tests/test_credentials.py 2011-01-02 03:34:23 +0000 | |||
226 | +++ ubuntu_sso/tests/test_credentials.py 2011-11-23 19:52:23 +0000 | |||
227 | @@ -3,7 +3,7 @@ | |||
228 | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> | 3 | # Author: Natalia Bidart <natalia.bidart@canonical.com> |
229 | 4 | # Author: Alejandro J. Cura <alecu@canonical.com> | 4 | # Author: Alejandro J. Cura <alecu@canonical.com> |
230 | 5 | # | 5 | # |
232 | 6 | # Copyright 2010 Canonical Ltd. | 6 | # Copyright 2010, 2011 Canonical Ltd. |
233 | 7 | # | 7 | # |
234 | 8 | # This program is free software: you can redistribute it and/or modify it | 8 | # This program is free software: you can redistribute it and/or modify it |
235 | 9 | # under the terms of the GNU General Public License version 3, as published | 9 | # under the terms of the GNU General Public License version 3, as published |
236 | @@ -20,6 +20,7 @@ | |||
237 | 20 | 20 | ||
238 | 21 | import logging | 21 | import logging |
239 | 22 | import urllib | 22 | import urllib |
240 | 23 | import time | ||
241 | 23 | 24 | ||
242 | 24 | from twisted.internet import defer | 25 | from twisted.internet import defer |
243 | 25 | from twisted.internet.defer import inlineCallbacks | 26 | from twisted.internet.defer import inlineCallbacks |
244 | @@ -359,6 +360,8 @@ | |||
245 | 359 | return response | 360 | return response |
246 | 360 | 361 | ||
247 | 361 | self.patch(credentials.urllib2, 'urlopen', faked_urlopen) | 362 | self.patch(credentials.urllib2, 'urlopen', faked_urlopen) |
248 | 363 | self.patch(credentials.utils.timestamp_checker, "get_faithful_time", | ||
249 | 364 | time.time) | ||
250 | 362 | 365 | ||
251 | 363 | @inlineCallbacks | 366 | @inlineCallbacks |
252 | 364 | def test_ping_url_if_url_is_none(self): | 367 | def test_ping_url_if_url_is_none(self): |
253 | 365 | 368 | ||
254 | === modified file 'ubuntu_sso/utils/__init__.py' | |||
255 | --- ubuntu_sso/utils/__init__.py 2010-11-15 23:06:52 +0000 | |||
256 | +++ ubuntu_sso/utils/__init__.py 2011-11-23 19:52:23 +0000 | |||
257 | @@ -2,7 +2,7 @@ | |||
258 | 2 | 2 | ||
259 | 3 | # Author: Alejandro J. Cura <alecu@canonical.com> | 3 | # Author: Alejandro J. Cura <alecu@canonical.com> |
260 | 4 | # | 4 | # |
262 | 5 | # Copyright 2010 Canonical Ltd. | 5 | # Copyright 2010, 2011 Canonical Ltd. |
263 | 6 | # | 6 | # |
264 | 7 | # This program is free software: you can redistribute it and/or modify it | 7 | # This program is free software: you can redistribute it and/or modify it |
265 | 8 | # under the terms of the GNU General Public License version 3, as published | 8 | # under the terms of the GNU General Public License version 3, as published |
266 | @@ -17,3 +17,65 @@ | |||
267 | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
268 | 18 | 18 | ||
269 | 19 | """Utility modules that may find use outside ubuntu_sso.""" | 19 | """Utility modules that may find use outside ubuntu_sso.""" |
270 | 20 | import time | ||
271 | 21 | import urllib2 | ||
272 | 22 | from twisted.web import http | ||
273 | 23 | |||
274 | 24 | from ubuntu_sso.logger import setup_logging | ||
275 | 25 | logger = setup_logging("ubuntu_sso.utils") | ||
276 | 26 | |||
277 | 27 | |||
278 | 28 | class RequestHead(urllib2.Request): | ||
279 | 29 | """A request with the method set to HEAD.""" | ||
280 | 30 | |||
281 | 31 | _request_method = "HEAD" | ||
282 | 32 | |||
283 | 33 | def get_method(self): | ||
284 | 34 | """Return the desired method.""" | ||
285 | 35 | return self._request_method | ||
286 | 36 | |||
287 | 37 | |||
288 | 38 | class SyncTimestampChecker(object): | ||
289 | 39 | """A timestamp that's regularly checked with a server.""" | ||
290 | 40 | |||
291 | 41 | CHECKING_INTERVAL = 60 * 60 # in seconds | ||
292 | 42 | ERROR_INTERVAL = 30 # in seconds | ||
293 | 43 | SERVER_URL = "http://one.ubuntu.com/api/time" | ||
294 | 44 | |||
295 | 45 | def __init__(self): | ||
296 | 46 | """Initialize this instance.""" | ||
297 | 47 | self.next_check = time.time() | ||
298 | 48 | self.skew = 0 | ||
299 | 49 | |||
300 | 50 | def get_server_time(self): | ||
301 | 51 | """Get the time at the server.""" | ||
302 | 52 | headers = {"Cache-Control": "no-cache"} | ||
303 | 53 | request = RequestHead(self.SERVER_URL, headers=headers) | ||
304 | 54 | response = urllib2.urlopen(request) | ||
305 | 55 | date_string = response.info()["Date"] | ||
306 | 56 | timestamp = http.stringToDatetime(date_string) | ||
307 | 57 | return timestamp | ||
308 | 58 | |||
309 | 59 | def get_faithful_time(self): | ||
310 | 60 | """Get an accurate timestamp.""" | ||
311 | 61 | local_time = time.time() | ||
312 | 62 | if local_time >= self.next_check: | ||
313 | 63 | try: | ||
314 | 64 | server_time = self.get_server_time() | ||
315 | 65 | self.next_check = local_time + self.CHECKING_INTERVAL | ||
316 | 66 | self.skew = server_time - local_time | ||
317 | 67 | logger.debug("Calculated server-local time skew: %r", | ||
318 | 68 | self.skew) | ||
319 | 69 | #pylint: disable=W0703 | ||
320 | 70 | except Exception, server_error: | ||
321 | 71 | logger.debug("Error while verifying the server time skew: %r", | ||
322 | 72 | server_error) | ||
323 | 73 | self.next_check = local_time + self.ERROR_INTERVAL | ||
324 | 74 | logger.debug("Using corrected timestamp: %r", | ||
325 | 75 | http.datetimeToString(local_time + self.skew)) | ||
326 | 76 | return int(local_time + self.skew) | ||
327 | 77 | |||
328 | 78 | |||
329 | 79 | # pylint: disable=C0103 | ||
330 | 80 | timestamp_checker = SyncTimestampChecker() | ||
331 | 81 | # pylint: enable=C0103 | ||
332 | 20 | 82 | ||
333 | === added file 'ubuntu_sso/utils/tests/test_oauth_headers.py' | |||
334 | --- ubuntu_sso/utils/tests/test_oauth_headers.py 1970-01-01 00:00:00 +0000 | |||
335 | +++ ubuntu_sso/utils/tests/test_oauth_headers.py 2011-11-23 19:52:23 +0000 | |||
336 | @@ -0,0 +1,148 @@ | |||
337 | 1 | # -*- coding: utf-8 -*- | ||
338 | 2 | |||
339 | 3 | # Author: Alejandro J. Cura <alecu@canonical.com> | ||
340 | 4 | # | ||
341 | 5 | # Copyright 2011 Canonical Ltd. | ||
342 | 6 | # | ||
343 | 7 | # This program is free software: you can redistribute it and/or modify it | ||
344 | 8 | # under the terms of the GNU General Public License version 3, as published | ||
345 | 9 | # by the Free Software Foundation. | ||
346 | 10 | # | ||
347 | 11 | # This program is distributed in the hope that it will be useful, but | ||
348 | 12 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
349 | 13 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
350 | 14 | # PURPOSE. See the GNU General Public License for more details. | ||
351 | 15 | # | ||
352 | 16 | # You should have received a copy of the GNU General Public License along | ||
353 | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
354 | 18 | |||
355 | 19 | """Tests for the oauth_headers helper function.""" | ||
356 | 20 | |||
357 | 21 | import time | ||
358 | 22 | |||
359 | 23 | from twisted.application import internet, service | ||
360 | 24 | from twisted.internet import defer | ||
361 | 25 | from twisted.internet.threads import deferToThread | ||
362 | 26 | from twisted.trial.unittest import TestCase | ||
363 | 27 | from twisted.web import server, resource | ||
364 | 28 | |||
365 | 29 | from ubuntu_sso.utils import SyncTimestampChecker | ||
366 | 30 | |||
367 | 31 | |||
368 | 32 | class RootResource(resource.Resource): | ||
369 | 33 | """A root resource that logs the number of calls.""" | ||
370 | 34 | |||
371 | 35 | isLeaf = True | ||
372 | 36 | |||
373 | 37 | def __init__(self, *args, **kwargs): | ||
374 | 38 | """Initialize this fake instance.""" | ||
375 | 39 | resource.Resource.__init__(self, *args, **kwargs) | ||
376 | 40 | self.count = 0 | ||
377 | 41 | self.request_headers = [] | ||
378 | 42 | |||
379 | 43 | # pylint: disable=C0103 | ||
380 | 44 | def render_HEAD(self, request): | ||
381 | 45 | """Increase the counter on each render.""" | ||
382 | 46 | self.count += 1 | ||
383 | 47 | self.request_headers.append(request.requestHeaders) | ||
384 | 48 | return "" | ||
385 | 49 | |||
386 | 50 | |||
387 | 51 | class MockWebServer(object): | ||
388 | 52 | """A mock webserver for testing.""" | ||
389 | 53 | |||
390 | 54 | def __init__(self): | ||
391 | 55 | """Start up this instance.""" | ||
392 | 56 | # pylint: disable=E1101 | ||
393 | 57 | self.root = RootResource() | ||
394 | 58 | site = server.Site(self.root) | ||
395 | 59 | application = service.Application('web') | ||
396 | 60 | self.service_collection = service.IServiceCollection(application) | ||
397 | 61 | self.tcpserver = internet.TCPServer(0, site) | ||
398 | 62 | self.tcpserver.setServiceParent(self.service_collection) | ||
399 | 63 | self.service_collection.startService() | ||
400 | 64 | |||
401 | 65 | def get_url(self): | ||
402 | 66 | """Build the url for this mock server.""" | ||
403 | 67 | # pylint: disable=W0212 | ||
404 | 68 | port_num = self.tcpserver._port.getHost().port | ||
405 | 69 | return "http://localhost:%d/" % port_num | ||
406 | 70 | |||
407 | 71 | def stop(self): | ||
408 | 72 | """Shut it down.""" | ||
409 | 73 | # pylint: disable=E1101 | ||
410 | 74 | self.service_collection.stopService() | ||
411 | 75 | |||
412 | 76 | |||
413 | 77 | class FakedError(Exception): | ||
414 | 78 | """A mock, test, sample, and fake exception.""" | ||
415 | 79 | |||
416 | 80 | |||
417 | 81 | class TimestampCheckerTestCase(TestCase): | ||
418 | 82 | """Tests for the timestamp checker.""" | ||
419 | 83 | |||
420 | 84 | def setUp(self): | ||
421 | 85 | """Initialize a fake webserver.""" | ||
422 | 86 | self.ws = MockWebServer() | ||
423 | 87 | self.addCleanup(self.ws.stop) | ||
424 | 88 | self.patch(SyncTimestampChecker, "SERVER_URL", self.ws.get_url()) | ||
425 | 89 | |||
426 | 90 | @defer.inlineCallbacks | ||
427 | 91 | def test_returned_value_is_int(self): | ||
428 | 92 | """The returned value is an integer.""" | ||
429 | 93 | checker = SyncTimestampChecker() | ||
430 | 94 | timestamp = yield deferToThread(checker.get_faithful_time) | ||
431 | 95 | self.assertEqual(type(timestamp), int) | ||
432 | 96 | |||
433 | 97 | @defer.inlineCallbacks | ||
434 | 98 | def test_first_call_does_head(self): | ||
435 | 99 | """The first call gets the clock from our web.""" | ||
436 | 100 | checker = SyncTimestampChecker() | ||
437 | 101 | yield deferToThread(checker.get_faithful_time) | ||
438 | 102 | self.assertEqual(self.ws.root.count, 1) | ||
439 | 103 | |||
440 | 104 | @defer.inlineCallbacks | ||
441 | 105 | def test_second_call_is_cached(self): | ||
442 | 106 | """For the second call, the time is cached.""" | ||
443 | 107 | checker = SyncTimestampChecker() | ||
444 | 108 | yield deferToThread(checker.get_faithful_time) | ||
445 | 109 | yield deferToThread(checker.get_faithful_time) | ||
446 | 110 | self.assertEqual(self.ws.root.count, 1) | ||
447 | 111 | |||
448 | 112 | @defer.inlineCallbacks | ||
449 | 113 | def test_after_timeout_cache_expires(self): | ||
450 | 114 | """After some time, the cache expires.""" | ||
451 | 115 | fake_timestamp = 1 | ||
452 | 116 | self.patch(time, "time", lambda: fake_timestamp) | ||
453 | 117 | checker = SyncTimestampChecker() | ||
454 | 118 | yield deferToThread(checker.get_faithful_time) | ||
455 | 119 | fake_timestamp += SyncTimestampChecker.CHECKING_INTERVAL | ||
456 | 120 | yield deferToThread(checker.get_faithful_time) | ||
457 | 121 | self.assertEqual(self.ws.root.count, 2) | ||
458 | 122 | |||
459 | 123 | @defer.inlineCallbacks | ||
460 | 124 | def test_server_date_sends_nocache_headers(self): | ||
461 | 125 | """Getting the server date sends the no-cache headers.""" | ||
462 | 126 | checker = SyncTimestampChecker() | ||
463 | 127 | yield deferToThread(checker.get_server_time) | ||
464 | 128 | assert len(self.ws.root.request_headers) == 1 | ||
465 | 129 | headers = self.ws.root.request_headers[0] | ||
466 | 130 | result = headers.getRawHeaders("Cache-Control") | ||
467 | 131 | self.assertEqual(result, ["no-cache"]) | ||
468 | 132 | |||
469 | 133 | @defer.inlineCallbacks | ||
470 | 134 | def test_server_error_means_skew_not_updated(self): | ||
471 | 135 | """When server can't be reached, the skew is not updated.""" | ||
472 | 136 | fake_timestamp = 1 | ||
473 | 137 | self.patch(time, "time", lambda: fake_timestamp) | ||
474 | 138 | checker = SyncTimestampChecker() | ||
475 | 139 | |||
476 | 140 | def failing_get_server_time(): | ||
477 | 141 | """Let's fail while retrieving the server time.""" | ||
478 | 142 | raise FakedError() | ||
479 | 143 | |||
480 | 144 | self.patch(checker, "get_server_time", failing_get_server_time) | ||
481 | 145 | yield deferToThread(checker.get_faithful_time) | ||
482 | 146 | self.assertEqual(checker.skew, 0) | ||
483 | 147 | self.assertEqual(checker.next_check, | ||
484 | 148 | fake_timestamp + SyncTimestampChecker.ERROR_INTERVAL) |
Text conflict in setup.py sso/account. py sso/credentials .py sso/tests/ test_credential s.py sso/utils/ __init_ _.py sso/utils/ tests/test_ oauth_headers. py sso/utils/ tests/test_ txsecrets. py
Text conflict in ubuntu_
Text conflict in ubuntu_
Text conflict in ubuntu_
Text conflict in ubuntu_
Text conflict in ubuntu_
Text conflict in ubuntu_
7 conflicts encountered.