Merge lp:~bac/launchpad/bug-750984 into lp:launchpad
- bug-750984
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Brad Crittenden | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 13127 | ||||
Proposed branch: | lp:~bac/launchpad/bug-750984 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
418 lines (+92/-55) 3 files modified
lib/canonical/launchpad/doc/webapp-publication.txt (+50/-33) lib/canonical/launchpad/interfaces/oauth.py (+22/-6) lib/canonical/launchpad/webapp/servers.py (+20/-16) |
||||
To merge this branch: | bzr merge lp:~bac/launchpad/bug-750984 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
j.c.sackett (community) | Approve | ||
Review via email: mp+62543@code.launchpad.net |
Commit message
[r=jcsackett][bug=750984] Don't OOPS on bad API token/nonce/
Description of the change
= Summary =
Previously problems with API token, nonce, or timestamp resulted in an
Unauthorized exception being raised which caused an OOPS to be
generated. Since the likely cause of those problems is bad client data
(though server side processing could be at fault) it is inappropriate to
generated an OOPS.
== Proposed fix ==
Rather than returning Unauthorized return custom exceptions that have
been marked with webservice_error as 401. The client still sees an HTTP
401 but no OOPS is reported.
== Pre-implementation notes ==
Talks with Gary and lots of conversation on the bug between Robert and
Martin.
== Tests ==
bin/test -vvt webapp-
== Demo and Q/A ==
It should be possible to force bad client data but the details are not
obvious at the moment.
= Launchpad lint =
The lint issues are not fixable.
Checking for conflicts and issues in changed files.
Linting changed files:
lib/canonical
lib/canonical
lib/canonical
./lib/canonical
195: want exceeds 78 characters.
203: want exceeds 78 characters.
325: want exceeds 78 characters.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/doc/webapp-publication.txt' | |||
2 | --- lib/canonical/launchpad/doc/webapp-publication.txt 2011-04-21 01:30:30 +0000 | |||
3 | +++ lib/canonical/launchpad/doc/webapp-publication.txt 2011-05-26 19:11:35 +0000 | |||
4 | @@ -1,4 +1,5 @@ | |||
6 | 1 | = Launchpad Publication = | 1 | Launchpad Publication |
7 | 2 | ===================== | ||
8 | 2 | 3 | ||
9 | 3 | Launchpad uses the generic Zope3 publisher. It registers several | 4 | Launchpad uses the generic Zope3 publisher. It registers several |
10 | 4 | factories that are responsible for instantiating the appropriate | 5 | factories that are responsible for instantiating the appropriate |
11 | @@ -6,7 +7,8 @@ | |||
12 | 6 | zope.publisher.IPublication for the request. | 7 | zope.publisher.IPublication for the request. |
13 | 7 | 8 | ||
14 | 8 | 9 | ||
16 | 9 | == Virtual host configurations == | 10 | Virtual host configurations |
17 | 11 | --------------------------- | ||
18 | 10 | 12 | ||
19 | 11 | The configuration defines a number of domains, one for the main | 13 | The configuration defines a number of domains, one for the main |
20 | 12 | Launchpad site and one for the sites of the various applications. | 14 | Launchpad site and one for the sites of the various applications. |
21 | @@ -118,7 +120,8 @@ | |||
22 | 118 | xmlrpc.launchpad.dev | 120 | xmlrpc.launchpad.dev |
23 | 119 | 121 | ||
24 | 120 | 122 | ||
26 | 121 | == VirtualHostRequestPublicationFactory == | 123 | VirtualHostRequestPublicationFactory |
27 | 124 | ------------------------------------ | ||
28 | 122 | 125 | ||
29 | 123 | A number of VirtualHostRequestPublicationFactories are registered with | 126 | A number of VirtualHostRequestPublicationFactories are registered with |
30 | 124 | Zope to handle requests for a particular vhost, port, and set of HTTP | 127 | Zope to handle requests for a particular vhost, port, and set of HTTP |
31 | @@ -322,7 +325,8 @@ | |||
32 | 322 | <class 'canonical.launchpad.webapp.publication.LaunchpadBrowserPublication'> | 325 | <class 'canonical.launchpad.webapp.publication.LaunchpadBrowserPublication'> |
33 | 323 | 326 | ||
34 | 324 | 327 | ||
36 | 325 | == Zope Publisher integration == | 328 | Zope Publisher integration |
37 | 329 | -------------------------- | ||
38 | 326 | 330 | ||
39 | 327 | A factory is registered for each of our available virtual host. This | 331 | A factory is registered for each of our available virtual host. This |
40 | 328 | is done by the register_launchpad_request_publication_factories | 332 | is done by the register_launchpad_request_publication_factories |
41 | @@ -417,7 +421,8 @@ | |||
42 | 417 | Allow: POST | 421 | Allow: POST |
43 | 418 | 422 | ||
44 | 419 | >>> print_request_and_publication( | 423 | >>> print_request_and_publication( |
46 | 420 | ... 'xmlrpc.launchpad.dev', method='POST', mime_type='application/xml') | 424 | ... 'xmlrpc.launchpad.dev', method='POST', |
47 | 425 | ... mime_type='application/xml') | ||
48 | 421 | ProtocolErrorRequest | 426 | ProtocolErrorRequest |
49 | 422 | ProtocolErrorPublication: status=415 | 427 | ProtocolErrorPublication: status=415 |
50 | 423 | 428 | ||
51 | @@ -483,7 +488,8 @@ | |||
52 | 483 | >>> login(ANONYMOUS) | 488 | >>> login(ANONYMOUS) |
53 | 484 | 489 | ||
54 | 485 | 490 | ||
56 | 486 | == ILaunchpadBrowserApplicationRequest == | 491 | ILaunchpadBrowserApplicationRequest |
57 | 492 | ----------------------------------- | ||
58 | 487 | 493 | ||
59 | 488 | All Launchpad requests provides the ILaunchpadBrowserApplicationRequest | 494 | All Launchpad requests provides the ILaunchpadBrowserApplicationRequest |
60 | 489 | interface. That interface is an extension of the zope standard | 495 | interface. That interface is an extension of the zope standard |
61 | @@ -497,7 +503,8 @@ | |||
62 | 497 | True | 503 | True |
63 | 498 | 504 | ||
64 | 499 | 505 | ||
66 | 500 | == Handling form data using IBrowserFormNG == | 506 | Handling form data using IBrowserFormNG |
67 | 507 | --------------------------------------- | ||
68 | 501 | 508 | ||
69 | 502 | Submitted form data is available in the form_ng request attribute. This | 509 | Submitted form data is available in the form_ng request attribute. This |
70 | 503 | is an object providing the IBrowserFormNG interface which offers two | 510 | is an object providing the IBrowserFormNG interface which offers two |
71 | @@ -582,7 +589,8 @@ | |||
72 | 582 | items_field | 589 | items_field |
73 | 583 | 590 | ||
74 | 584 | 591 | ||
76 | 585 | == Page ID == | 592 | Page ID |
77 | 593 | ------- | ||
78 | 586 | 594 | ||
79 | 587 | Our publication implementation sets a WSGI variable 'launchpad.pageid'. | 595 | Our publication implementation sets a WSGI variable 'launchpad.pageid'. |
80 | 588 | This is an identifier of the form ContextName:ViewName. | 596 | This is an identifier of the form ContextName:ViewName. |
81 | @@ -654,7 +662,8 @@ | |||
82 | 654 | '' | 662 | '' |
83 | 655 | 663 | ||
84 | 656 | 664 | ||
86 | 657 | == Tick counts == | 665 | Tick counts |
87 | 666 | ----------- | ||
88 | 658 | 667 | ||
89 | 659 | Similarly to our page IDs, our publication implementation will store the | 668 | Similarly to our page IDs, our publication implementation will store the |
90 | 660 | tick counts for the traversal and object publication processes in WSGI | 669 | tick counts for the traversal and object publication processes in WSGI |
91 | @@ -832,7 +841,8 @@ | |||
92 | 832 | >>> login(ANONYMOUS) | 841 | >>> login(ANONYMOUS) |
93 | 833 | 842 | ||
94 | 834 | 843 | ||
96 | 835 | == Transaction Logging == | 844 | Transaction Logging |
97 | 845 | ------------------- | ||
98 | 836 | 846 | ||
99 | 837 | The publication implementation is responsible for putting the name | 847 | The publication implementation is responsible for putting the name |
100 | 838 | of the logged in user in the transaction. (The afterCall() hook is | 848 | of the logged in user in the transaction. (The afterCall() hook is |
101 | @@ -872,7 +882,8 @@ | |||
102 | 872 | / 16 | 882 | / 16 |
103 | 873 | 883 | ||
104 | 874 | 884 | ||
106 | 875 | == Read-Only Requests == | 885 | Read-Only Requests |
107 | 886 | ------------------ | ||
108 | 876 | 887 | ||
109 | 877 | Our publication implementation make sure that requests supposed to be | 888 | Our publication implementation make sure that requests supposed to be |
110 | 878 | read-only (GET and HEAD) don't change anything in the database. | 889 | read-only (GET and HEAD) don't change anything in the database. |
111 | @@ -943,7 +954,8 @@ | |||
112 | 943 | Darwin | 954 | Darwin |
113 | 944 | 955 | ||
114 | 945 | 956 | ||
116 | 946 | === GET requests on api.launchpad.net === | 957 | GET requests on api.launchpad.net |
117 | 958 | ................................. | ||
118 | 947 | 959 | ||
119 | 948 | In the case of WebServicePublication, though, we have to commit the | 960 | In the case of WebServicePublication, though, we have to commit the |
120 | 949 | transaction after GET requests in order to persist new entries added to | 961 | transaction after GET requests in order to persist new entries added to |
121 | @@ -992,13 +1004,14 @@ | |||
122 | 992 | ... 'api.launchpad.dev', 'GET', | 1004 | ... 'api.launchpad.dev', 'GET', |
123 | 993 | ... extra_environment=dict(QUERY_STRING=urlencode(form))) | 1005 | ... extra_environment=dict(QUERY_STRING=urlencode(form))) |
124 | 994 | >>> test_request.processInputs() | 1006 | >>> test_request.processInputs() |
126 | 995 | >>> print publication.getPrincipal(test_request).title | 1007 | >>> publication.getPrincipal(test_request) |
127 | 996 | Traceback (most recent call last): | 1008 | Traceback (most recent call last): |
128 | 997 | ... | 1009 | ... |
133 | 998 | Unauthorized: Invalid nonce/timestamp: This nonce has been used already. | 1010 | NonceAlreadyUsed: This nonce has been used already. |
134 | 999 | 1011 | ||
135 | 1000 | 1012 | ||
136 | 1001 | == Doomed transactions are aborted == | 1013 | Doomed transactions are aborted |
137 | 1014 | ------------------------------- | ||
138 | 1002 | 1015 | ||
139 | 1003 | Doomed transactions are aborted. | 1016 | Doomed transactions are aborted. |
140 | 1004 | 1017 | ||
141 | @@ -1032,7 +1045,8 @@ | |||
142 | 1032 | >>> del txn.abort # Clean up test fixture. | 1045 | >>> del txn.abort # Clean up test fixture. |
143 | 1033 | 1046 | ||
144 | 1034 | 1047 | ||
146 | 1035 | == Requests on Python C Methods succeed == | 1048 | Requests on Python C Methods succeed |
147 | 1049 | ------------------------------------ | ||
148 | 1036 | 1050 | ||
149 | 1037 | Rarely but occasionally, it is possible to traverse to a Python C method. | 1051 | Rarely but occasionally, it is possible to traverse to a Python C method. |
150 | 1038 | For instance, an XMLRPC proxy might allow a traversal to __repr__. | 1052 | For instance, an XMLRPC proxy might allow a traversal to __repr__. |
151 | @@ -1048,7 +1062,8 @@ | |||
152 | 1048 | '{}' | 1062 | '{}' |
153 | 1049 | 1063 | ||
154 | 1050 | 1064 | ||
156 | 1051 | == HEAD requests have empty body == | 1065 | HEAD requests have empty body |
157 | 1066 | ----------------------------- | ||
158 | 1052 | 1067 | ||
159 | 1053 | The publication implementation also makes sure that no body is | 1068 | The publication implementation also makes sure that no body is |
160 | 1054 | returned as part of HEAD requests. (Again this is handled by the | 1069 | returned as part of HEAD requests. (Again this is handled by the |
161 | @@ -1085,7 +1100,8 @@ | |||
162 | 1085 | Some boring content. | 1100 | Some boring content. |
163 | 1086 | 1101 | ||
164 | 1087 | 1102 | ||
166 | 1088 | == Authentication of requests == | 1103 | Authentication of requests |
167 | 1104 | -------------------------- | ||
168 | 1089 | 1105 | ||
169 | 1090 | In LaunchpadBrowserPublication, authentication happens in the | 1106 | In LaunchpadBrowserPublication, authentication happens in the |
170 | 1091 | beforeTraversal() hook. Our publication will set the principal to | 1107 | beforeTraversal() hook. Our publication will set the principal to |
171 | @@ -1183,10 +1199,11 @@ | |||
172 | 1183 | >>> form2 = form.copy() | 1199 | >>> form2 = form.copy() |
173 | 1184 | >>> form2['oauth_nonce'] = '1764572616e48616d6d65724c61686' | 1200 | >>> form2['oauth_nonce'] = '1764572616e48616d6d65724c61686' |
174 | 1185 | >>> test_request = LaunchpadTestRequest(form=form2) | 1201 | >>> test_request = LaunchpadTestRequest(form=form2) |
176 | 1186 | >>> print publication.getPrincipal(test_request).title | 1202 | >>> publication.getPrincipal(test_request) |
177 | 1187 | Traceback (most recent call last): | 1203 | Traceback (most recent call last): |
178 | 1188 | ... | 1204 | ... |
180 | 1189 | Unauthorized: Expired token... | 1205 | TokenException: Expired token... |
181 | 1206 | |||
182 | 1190 | >>> access_token.date_expires = now + timedelta(days=1) | 1207 | >>> access_token.date_expires = now + timedelta(days=1) |
183 | 1191 | >>> syncUpdate(access_token) | 1208 | >>> syncUpdate(access_token) |
184 | 1192 | 1209 | ||
185 | @@ -1194,10 +1211,10 @@ | |||
186 | 1194 | >>> form2['oauth_token'] += 'z' | 1211 | >>> form2['oauth_token'] += 'z' |
187 | 1195 | >>> form2['oauth_nonce'] = '4572616e48616d6d65724c61686176' | 1212 | >>> form2['oauth_nonce'] = '4572616e48616d6d65724c61686176' |
188 | 1196 | >>> test_request = LaunchpadTestRequest(form=form2) | 1213 | >>> test_request = LaunchpadTestRequest(form=form2) |
190 | 1197 | >>> print publication.getPrincipal(test_request).title | 1214 | >>> publication.getPrincipal(test_request) |
191 | 1198 | Traceback (most recent call last): | 1215 | Traceback (most recent call last): |
192 | 1199 | ... | 1216 | ... |
194 | 1200 | Unauthorized: Unknown access token... | 1217 | TokenException: Unknown access token... |
195 | 1201 | 1218 | ||
196 | 1202 | The consumer must be registered as well, and the signature must be | 1219 | The consumer must be registered as well, and the signature must be |
197 | 1203 | correct. | 1220 | correct. |
198 | @@ -1205,7 +1222,7 @@ | |||
199 | 1205 | >>> form2 = form.copy() | 1222 | >>> form2 = form.copy() |
200 | 1206 | >>> form2['oauth_consumer_key'] += 'z' | 1223 | >>> form2['oauth_consumer_key'] += 'z' |
201 | 1207 | >>> test_request = LaunchpadTestRequest(form=form2) | 1224 | >>> test_request = LaunchpadTestRequest(form=form2) |
203 | 1208 | >>> print publication.getPrincipal(test_request).title | 1225 | >>> publication.getPrincipal(test_request) |
204 | 1209 | Traceback (most recent call last): | 1226 | Traceback (most recent call last): |
205 | 1210 | ... | 1227 | ... |
206 | 1211 | Unauthorized: Unknown consumer (foobar123451432z). | 1228 | Unauthorized: Unknown consumer (foobar123451432z). |
207 | @@ -1214,10 +1231,10 @@ | |||
208 | 1214 | >>> form2['oauth_signature'] += 'z' | 1231 | >>> form2['oauth_signature'] += 'z' |
209 | 1215 | >>> form2['oauth_nonce'] = '2616e48616d6d65724c61686176457' | 1232 | >>> form2['oauth_nonce'] = '2616e48616d6d65724c61686176457' |
210 | 1216 | >>> test_request = LaunchpadTestRequest(form=form2) | 1233 | >>> test_request = LaunchpadTestRequest(form=form2) |
212 | 1217 | >>> print publication.getPrincipal(test_request).title | 1234 | >>> publication.getPrincipal(test_request) |
213 | 1218 | Traceback (most recent call last): | 1235 | Traceback (most recent call last): |
214 | 1219 | ... | 1236 | ... |
216 | 1220 | Unauthorized: Invalid signature. | 1237 | TokenException: Invalid signature. |
217 | 1221 | 1238 | ||
218 | 1222 | The nonce specified in the request must not have been used before in a request | 1239 | The nonce specified in the request must not have been used before in a request |
219 | 1223 | with this same token and timestamp. | 1240 | with this same token and timestamp. |
220 | @@ -1229,10 +1246,10 @@ | |||
221 | 1229 | Guilherme Salgado | 1246 | Guilherme Salgado |
222 | 1230 | 1247 | ||
223 | 1231 | >>> test_request = LaunchpadTestRequest(form=form2) | 1248 | >>> test_request = LaunchpadTestRequest(form=form2) |
225 | 1232 | >>> print publication.getPrincipal(test_request).title | 1249 | >>> publication.getPrincipal(test_request) |
226 | 1233 | Traceback (most recent call last): | 1250 | Traceback (most recent call last): |
227 | 1234 | ... | 1251 | ... |
229 | 1235 | Unauthorized: Invalid nonce/timestamp: This nonce has been used already. | 1252 | NonceAlreadyUsed: This nonce has been used already. |
230 | 1236 | 1253 | ||
231 | 1237 | The timestamp must not be older than TIMESTAMP_ACCEPTANCE_WINDOW from the most | 1254 | The timestamp must not be older than TIMESTAMP_ACCEPTANCE_WINDOW from the most |
232 | 1238 | recent request for this token. | 1255 | recent request for this token. |
233 | @@ -1247,10 +1264,10 @@ | |||
234 | 1247 | 1264 | ||
235 | 1248 | >>> form2['oauth_timestamp'] -= 10 | 1265 | >>> form2['oauth_timestamp'] -= 10 |
236 | 1249 | >>> test_request = LaunchpadTestRequest(form=form2) | 1266 | >>> test_request = LaunchpadTestRequest(form=form2) |
238 | 1250 | >>> print publication.getPrincipal(test_request).title | 1267 | >>> publication.getPrincipal(test_request) |
239 | 1251 | Traceback (most recent call last): | 1268 | Traceback (most recent call last): |
240 | 1252 | ... | 1269 | ... |
242 | 1253 | Unauthorized: Invalid nonce/timestamp: Timestamp too old compared ... | 1270 | TimestampOrderingError: Timestamp too old compared ... |
243 | 1254 | 1271 | ||
244 | 1255 | Last but not least, the timestamp must not be too far in the future, as | 1272 | Last but not least, the timestamp must not be too far in the future, as |
245 | 1256 | defined by TIMESTAMP_SKEW_WINDOW. | 1273 | defined by TIMESTAMP_SKEW_WINDOW. |
246 | @@ -1260,10 +1277,10 @@ | |||
247 | 1260 | >>> form2 = form.copy() | 1277 | >>> form2 = form.copy() |
248 | 1261 | >>> form2['oauth_timestamp'] += (TIMESTAMP_SKEW_WINDOW+10) | 1278 | >>> form2['oauth_timestamp'] += (TIMESTAMP_SKEW_WINDOW+10) |
249 | 1262 | >>> test_request = LaunchpadTestRequest(form=form2) | 1279 | >>> test_request = LaunchpadTestRequest(form=form2) |
251 | 1263 | >>> print publication.getPrincipal(test_request).title | 1280 | >>> publication.getPrincipal(test_request) |
252 | 1264 | Traceback (most recent call last): | 1281 | Traceback (most recent call last): |
253 | 1265 | ... | 1282 | ... |
255 | 1266 | Unauthorized: Invalid nonce/timestamp: Timestamp ... from bad system clock | 1283 | ClockSkew: Timestamp ... from bad system clock |
256 | 1267 | 1284 | ||
257 | 1268 | Close the bogus request that was started by the call to | 1285 | Close the bogus request that was started by the call to |
258 | 1269 | beforeTraversal, in order to ensure we leave our state sane. | 1286 | beforeTraversal, in order to ensure we leave our state sane. |
259 | 1270 | 1287 | ||
260 | === modified file 'lib/canonical/launchpad/interfaces/oauth.py' | |||
261 | --- lib/canonical/launchpad/interfaces/oauth.py 2010-10-18 11:34:31 +0000 | |||
262 | +++ lib/canonical/launchpad/interfaces/oauth.py 2011-05-26 19:11:35 +0000 | |||
263 | @@ -20,8 +20,10 @@ | |||
264 | 20 | 'NonceAlreadyUsed', | 20 | 'NonceAlreadyUsed', |
265 | 21 | 'TimestampOrderingError', | 21 | 'TimestampOrderingError', |
266 | 22 | 'ClockSkew', | 22 | 'ClockSkew', |
267 | 23 | 'TokenException', | ||
268 | 23 | ] | 24 | ] |
269 | 24 | 25 | ||
270 | 26 | import httplib | ||
271 | 25 | from zope.interface import ( | 27 | from zope.interface import ( |
272 | 26 | Attribute, | 28 | Attribute, |
273 | 27 | Interface, | 29 | Interface, |
274 | @@ -34,6 +36,8 @@ | |||
275 | 34 | TextLine, | 36 | TextLine, |
276 | 35 | ) | 37 | ) |
277 | 36 | 38 | ||
278 | 39 | from lazr.restful.declarations import webservice_error | ||
279 | 40 | |||
280 | 37 | from canonical.launchpad import _ | 41 | from canonical.launchpad import _ |
281 | 38 | from canonical.launchpad.webapp.interfaces import ( | 42 | from canonical.launchpad.webapp.interfaces import ( |
282 | 39 | AccessLevel, | 43 | AccessLevel, |
283 | @@ -300,14 +304,26 @@ | |||
284 | 300 | """Marker interface for a request signed with OAuth credentials.""" | 304 | """Marker interface for a request signed with OAuth credentials.""" |
285 | 301 | 305 | ||
286 | 302 | 306 | ||
291 | 303 | # Note that these three exceptions are converted to Unauthorized (equating to | 307 | # Note that these exceptions are marked as UNAUTHORIZED (401 status) |
292 | 304 | # 401 status) in webapp/servers.py, WebServicePublication.getPrincipal. | 308 | # so they may be raised but will not cause an OOPS to be generated. The |
293 | 305 | 309 | # client will see them as an UNAUTHORIZED error. | |
294 | 306 | class NonceAlreadyUsed(Exception): | 310 | |
295 | 311 | class _TokenException(Exception): | ||
296 | 312 | """Base class for token, nonce, and timestamp exceptions.""" | ||
297 | 313 | webservice_error(httplib.UNAUTHORIZED) | ||
298 | 314 | |||
299 | 315 | |||
300 | 316 | class NonceAlreadyUsed(_TokenException): | ||
301 | 307 | """Nonce has been used together with same token but another timestamp.""" | 317 | """Nonce has been used together with same token but another timestamp.""" |
302 | 308 | 318 | ||
304 | 309 | class TimestampOrderingError(Exception): | 319 | |
305 | 320 | class TimestampOrderingError(_TokenException): | ||
306 | 310 | """Timestamp is too old, compared to the last request.""" | 321 | """Timestamp is too old, compared to the last request.""" |
307 | 311 | 322 | ||
309 | 312 | class ClockSkew(Exception): | 323 | |
310 | 324 | class ClockSkew(_TokenException): | ||
311 | 313 | """Timestamp is too far off from server's clock.""" | 325 | """Timestamp is too far off from server's clock.""" |
312 | 326 | |||
313 | 327 | |||
314 | 328 | class TokenException(_TokenException): | ||
315 | 329 | """Token has expired.""" | ||
316 | 314 | 330 | ||
317 | === modified file 'lib/canonical/launchpad/webapp/servers.py' | |||
318 | --- lib/canonical/launchpad/webapp/servers.py 2011-05-20 19:35:04 +0000 | |||
319 | +++ lib/canonical/launchpad/webapp/servers.py 2011-05-26 19:11:35 +0000 | |||
320 | @@ -67,11 +67,9 @@ | |||
321 | 67 | IWebServiceApplication, | 67 | IWebServiceApplication, |
322 | 68 | ) | 68 | ) |
323 | 69 | from canonical.launchpad.interfaces.oauth import ( | 69 | from canonical.launchpad.interfaces.oauth import ( |
324 | 70 | ClockSkew, | ||
325 | 71 | IOAuthConsumerSet, | 70 | IOAuthConsumerSet, |
326 | 72 | IOAuthSignedRequest, | 71 | IOAuthSignedRequest, |
329 | 73 | NonceAlreadyUsed, | 72 | TokenException, |
328 | 74 | TimestampOrderingError, | ||
330 | 75 | ) | 73 | ) |
331 | 76 | import canonical.launchpad.layers | 74 | import canonical.launchpad.layers |
332 | 77 | from canonical.launchpad.webapp.authentication import ( | 75 | from canonical.launchpad.webapp.authentication import ( |
333 | @@ -1025,7 +1023,7 @@ | |||
334 | 1025 | 1023 | ||
335 | 1026 | 1024 | ||
336 | 1027 | http = wsgi.ServerType( | 1025 | http = wsgi.ServerType( |
338 | 1028 | ZServerTracelogServer, # subclass of WSGIHTTPServer | 1026 | ZServerTracelogServer, # subclass of WSGIHTTPServer |
339 | 1029 | WSGIPublisherApplication, | 1027 | WSGIPublisherApplication, |
340 | 1030 | LaunchpadAccessLogger, | 1028 | LaunchpadAccessLogger, |
341 | 1031 | 8080, | 1029 | 8080, |
342 | @@ -1039,7 +1037,7 @@ | |||
343 | 1039 | True) | 1037 | True) |
344 | 1040 | 1038 | ||
345 | 1041 | debughttp = wsgi.ServerType( | 1039 | debughttp = wsgi.ServerType( |
347 | 1042 | ZServerTracelogServer, # subclass of WSGIHTTPServer | 1040 | ZServerTracelogServer, # subclass of WSGIHTTPServer |
348 | 1043 | WSGIPublisherApplication, | 1041 | WSGIPublisherApplication, |
349 | 1044 | LaunchpadAccessLogger, | 1042 | LaunchpadAccessLogger, |
350 | 1045 | 8082, | 1043 | 8082, |
351 | @@ -1047,7 +1045,7 @@ | |||
352 | 1047 | requestFactory=DebugLayerRequestFactory) | 1045 | requestFactory=DebugLayerRequestFactory) |
353 | 1048 | 1046 | ||
354 | 1049 | privatexmlrpc = wsgi.ServerType( | 1047 | privatexmlrpc = wsgi.ServerType( |
356 | 1050 | ZServerTracelogServer, # subclass of WSGIHTTPServer | 1048 | ZServerTracelogServer, # subclass of WSGIHTTPServer |
357 | 1051 | WSGIPublisherApplication, | 1049 | WSGIPublisherApplication, |
358 | 1052 | LaunchpadAccessLogger, | 1050 | LaunchpadAccessLogger, |
359 | 1053 | 8080, | 1051 | 8080, |
360 | @@ -1208,6 +1206,14 @@ | |||
361 | 1208 | 1206 | ||
362 | 1209 | Web service requests are authenticated using OAuth, except for the | 1207 | Web service requests are authenticated using OAuth, except for the |
363 | 1210 | one made using (presumably) JavaScript on the /api override path. | 1208 | one made using (presumably) JavaScript on the /api override path. |
364 | 1209 | |||
365 | 1210 | Raises a variety of token errors (ClockSkew, NonceAlreadyUsed, | ||
366 | 1211 | TimestampOrderingError, TokenException) which have a webservice error | ||
367 | 1212 | status of Unauthorized - 401. All of these exceptions represent | ||
368 | 1213 | errors on the part of the client. | ||
369 | 1214 | |||
370 | 1215 | Raises Unauthorized directly in the case where the consumer is None | ||
371 | 1216 | for a non-anonymous request as it may represent a server error. | ||
372 | 1211 | """ | 1217 | """ |
373 | 1212 | # Use the regular HTTP authentication, when the request is not | 1218 | # Use the regular HTTP authentication, when the request is not |
374 | 1213 | # on the API virtual host but comes through the path_override on | 1219 | # on the API virtual host but comes through the path_override on |
375 | @@ -1250,7 +1256,7 @@ | |||
376 | 1250 | # transactions committed so that we can keep track of | 1256 | # transactions committed so that we can keep track of |
377 | 1251 | # the OAuth nonces and prevent replay attacks. | 1257 | # the OAuth nonces and prevent replay attacks. |
378 | 1252 | if consumer_key == '' or consumer_key is None: | 1258 | if consumer_key == '' or consumer_key is None: |
380 | 1253 | raise Unauthorized("No consumer key specified.") | 1259 | raise TokenException("No consumer key specified.") |
381 | 1254 | consumer = consumers.new(consumer_key, '') | 1260 | consumer = consumers.new(consumer_key, '') |
382 | 1255 | else: | 1261 | else: |
383 | 1256 | # An unknown consumer can never make a non-anonymous | 1262 | # An unknown consumer can never make a non-anonymous |
384 | @@ -1270,19 +1276,16 @@ | |||
385 | 1270 | return auth_utility.unauthenticatedPrincipal() | 1276 | return auth_utility.unauthenticatedPrincipal() |
386 | 1271 | token = consumer.getAccessToken(token_key) | 1277 | token = consumer.getAccessToken(token_key) |
387 | 1272 | if token is None: | 1278 | if token is None: |
389 | 1273 | raise Unauthorized('Unknown access token (%s).' % token_key) | 1279 | raise TokenException('Unknown access token (%s).' % token_key) |
390 | 1274 | nonce = form.get('oauth_nonce') | 1280 | nonce = form.get('oauth_nonce') |
391 | 1275 | timestamp = form.get('oauth_timestamp') | 1281 | timestamp = form.get('oauth_timestamp') |
396 | 1276 | try: | 1282 | token.checkNonceAndTimestamp(nonce, timestamp) |
393 | 1277 | token.checkNonceAndTimestamp(nonce, timestamp) | ||
394 | 1278 | except (NonceAlreadyUsed, TimestampOrderingError, ClockSkew), e: | ||
395 | 1279 | raise Unauthorized('Invalid nonce/timestamp: %s' % e) | ||
397 | 1280 | if token.permission == OAuthPermission.UNAUTHORIZED: | 1283 | if token.permission == OAuthPermission.UNAUTHORIZED: |
399 | 1281 | raise Unauthorized('Unauthorized token (%s).' % token.key) | 1284 | raise TokenException('Unauthorized token (%s).' % token.key) |
400 | 1282 | elif token.is_expired: | 1285 | elif token.is_expired: |
402 | 1283 | raise Unauthorized('Expired token (%s).' % token.key) | 1286 | raise TokenException('Expired token (%s).' % token.key) |
403 | 1284 | elif not check_oauth_signature(request, consumer, token): | 1287 | elif not check_oauth_signature(request, consumer, token): |
405 | 1285 | raise Unauthorized('Invalid signature.') | 1288 | raise TokenException('Invalid signature.') |
406 | 1286 | else: | 1289 | else: |
407 | 1287 | # Everything is fine, let's return the principal. | 1290 | # Everything is fine, let's return the principal. |
408 | 1288 | pass | 1291 | pass |
409 | @@ -1529,7 +1532,8 @@ | |||
410 | 1529 | # len(factories)+1. | 1532 | # len(factories)+1. |
411 | 1530 | for priority, factory in enumerate(factories): | 1533 | for priority, factory in enumerate(factories): |
412 | 1531 | publisher_factory_registry.register( | 1534 | publisher_factory_registry.register( |
414 | 1532 | "*", "*", factory.vhost_name, len(factories)-priority+1, factory) | 1535 | "*", "*", factory.vhost_name, len(factories) - priority + 1, |
415 | 1536 | factory) | ||
416 | 1533 | 1537 | ||
417 | 1534 | # Register a catch-all "not found" handler at the lowest priority. | 1538 | # Register a catch-all "not found" handler at the lowest priority. |
418 | 1535 | publisher_factory_registry.register( | 1539 | publisher_factory_registry.register( |
This looks good to land.