Merge lp:~allenap/maas/break-on-invalid-oauth into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 1214
Proposed branch: lp:~allenap/maas/break-on-invalid-oauth
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~allenap/maas/describe-anon-handlers
Diff against target: 90 lines (+54/-6)
2 files modified
src/maasserver/api_auth.py (+37/-6)
src/maasserver/tests/test_api.py (+17/-0)
To merge this branch: bzr merge lp:~allenap/maas/break-on-invalid-oauth
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+128377@code.launchpad.net

Commit message

Reject invalid OAuth requests instead of silently downgrading them.

Previously no mention would be made of invalid OAuth credentials, and requests would be silently downgraded to anonymous, with puzzling results.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Perfick.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

Attempt to merge into lp:maas failed due to conflicts:

text conflict in src/maasserver/tests/test_api.py

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api_auth.py'
2--- src/maasserver/api_auth.py 2012-04-16 10:00:51 +0000
3+++ src/maasserver/api_auth.py 2012-10-08 11:20:28 +0000
4@@ -14,22 +14,53 @@
5 'api_auth',
6 ]
7
8-from piston.authentication import OAuthAuthentication
9+from maasserver.exceptions import Unauthorized
10+from oauth import oauth
11+from piston.authentication import (
12+ OAuthAuthentication,
13+ send_oauth_error,
14+ )
15 from piston.utils import rc
16
17
18+class OAuthUnauthorized(Unauthorized):
19+ """Unauthorized error for OAuth signed requests with invalid tokens."""
20+
21+ def __init__(self, error):
22+ super(OAuthUnauthorized, self).__init__()
23+ self.error = error
24+
25+ def make_http_response(self):
26+ return send_oauth_error(self.error)
27+
28+
29 class MAASAPIAuthentication(OAuthAuthentication):
30- """A piston authentication class that uses the currently logged-in user
31- if there is one, and defaults to piston's OAuthAuthentication if not.
32+ """Use the currently logged-in user; resort to OAuth if there isn't one.
33
34+ There may be a user already logged-in via another mechanism, like a
35+ familiar in-browser user/pass challenge.
36 """
37
38 def is_authenticated(self, request):
39 if request.user.is_authenticated():
40 return request.user
41- else:
42- return super(
43- MAASAPIAuthentication, self).is_authenticated(request)
44+
45+ # The following is much the same as is_authenticated from Piston's
46+ # OAuthAuthentication, with the difference that an OAuth request that
47+ # does not validate is rejected instead of being silently downgraded.
48+ if self.is_valid_request(request):
49+ try:
50+ consumer, token, parameters = self.validate_token(request)
51+ except oauth.OAuthError as error:
52+ raise OAuthUnauthorized(error)
53+
54+ if consumer and token:
55+ request.user = token.user
56+ request.consumer = consumer
57+ request.throttle_extra = token.consumer.id
58+ return True
59+
60+ return False
61
62 def challenge(self):
63 # Beware: this returns 401: Unauthorized, not 403: Forbidden
64
65=== modified file 'src/maasserver/tests/test_api.py'
66--- src/maasserver/tests/test_api.py 2012-10-08 10:32:48 +0000
67+++ src/maasserver/tests/test_api.py 2012-10-08 11:20:28 +0000
68@@ -225,6 +225,23 @@
69 self.assertEqual([data_value], results.getlist(key))
70
71
72+class TestAuthentication(APIv10TestMixin, TestCase):
73+ """Tests for `maasserver.api_auth`."""
74+
75+ def test_invalid_oauth_request(self):
76+ # An OAuth-signed request that does not validate is an error.
77+ user = factory.make_user()
78+ client = OAuthAuthenticatedClient(user)
79+ get_auth_tokens(user).delete() # Delete the user's API keys.
80+ response = client.post(self.get_uri('nodes/'), {'op': 'start'})
81+ observed = response.status_code, response.content
82+ expected = (
83+ Equals(httplib.UNAUTHORIZED),
84+ Contains("Invalid access token:"),
85+ )
86+ self.assertThat(observed, MatchesListwise(expected))
87+
88+
89 class TestStoreNodeParameters(TestCase):
90 """Tests for `store_node_power_parameters`."""
91