Merge lp:~allenap/maas/version-without-auth--bug-1583715--1.9 into lp:maas/1.9

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 4582
Proposed branch: lp:~allenap/maas/version-without-auth--bug-1583715--1.9
Merge into: lp:maas/1.9
Diff against target: 206 lines (+110/-5)
4 files modified
src/maasserver/api/support.py (+26/-0)
src/maasserver/api/tests/test_support.py (+67/-1)
src/maasserver/api/tests/test_version.py (+15/-3)
src/maasserver/urls_api.py (+2/-1)
To merge this branch: bzr merge lp:~allenap/maas/version-without-auth--bug-1583715--1.9
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+295837@code.launchpad.net

Commit message

Backport r5039 from lp:maas. Ensure that restricted resources also perform meaningful authentication of clients.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Backport; self-reviewing.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/support.py'
2--- src/maasserver/api/support.py 2016-04-13 16:34:23 +0000
3+++ src/maasserver/api/support.py 2016-05-26 14:12:20 +0000
4@@ -25,6 +25,7 @@
5 from django.http import Http404
6 from maasserver.api.doc import get_api_description_hash
7 from maasserver.exceptions import MAASAPIBadRequest
8+from piston.authentication import NoAuthentication
9 from piston.emitters import Emitter
10 from piston.handler import (
11 AnonymousBaseHandler,
12@@ -66,10 +67,35 @@
13 else:
14 raise
15
16+ @property
17+ def is_authentication_attempted(self):
18+ """Will use of this resource attempt to authenticate the client?
19+
20+ For example, `None`, ``[]``, and :class:`NoAuthentication` are all
21+ examples of authentication handlers that do *not* count.
22+ """
23+ return len(self.authentication) != 0 and not any(
24+ isinstance(auth, NoAuthentication) for auth in self.authentication)
25+
26
27 class RestrictedResource(OperationsResource):
28 """A resource that's restricted to active users."""
29
30+ def __init__(self, handler, authentication):
31+ """A value for `authentication` MUST be provided AND be meaningful.
32+
33+ This prevents the situation where none of the following are restricted
34+ at all::
35+
36+ handler = RestrictedResource(HandlerClass)
37+ handler = RestrictedResource(HandlerClass, authentication=None)
38+ handler = RestrictedResource(HandlerClass, authentication=[])
39+
40+ """
41+ super(RestrictedResource, self).__init__(handler, authentication)
42+ if not self.is_authentication_attempted:
43+ raise AssertionError("Authentication must be attempted.")
44+
45 def authenticate(self, request, rm):
46 actor, anonymous = super(
47 RestrictedResource, self).authenticate(request, rm)
48
49=== modified file 'src/maasserver/api/tests/test_support.py'
50--- src/maasserver/api/tests/test_support.py 2015-04-21 22:51:42 +0000
51+++ src/maasserver/api/tests/test_support.py 2016-05-26 14:12:20 +0000
52@@ -23,7 +23,10 @@
53 from maasserver.api.doc import get_api_description_hash
54 from maasserver.api.support import (
55 admin_method,
56+ AdminRestrictedResource,
57 OperationsHandlerMixin,
58+ OperationsResource,
59+ RestrictedResource,
60 )
61 from maasserver.models.config import (
62 Config,
63@@ -37,7 +40,17 @@
64 Mock,
65 sentinel,
66 )
67-from testtools.matchers import Equals
68+from piston.authentication import NoAuthentication
69+from testtools.matchers import (
70+ Equals,
71+ Is,
72+)
73+
74+
75+class StubHandler:
76+ """A stub handler class that breaks when called."""
77+ def __call__(self, request):
78+ raise AssertionError("Do not call the stub handler.")
79
80
81 class TestOperationsResource(APITestCase):
82@@ -76,6 +89,59 @@
83 response["X-MAAS-API-Hash"],
84 Equals(get_api_description_hash()))
85
86+ def test_authenticated_is_False_when_no_authentication_provided(self):
87+ resource = OperationsResource(StubHandler)
88+ self.assertThat(resource.is_authentication_attempted, Is(False))
89+
90+ def test_authenticated_is_False_when_authentication_is_empty(self):
91+ resource = OperationsResource(StubHandler, authentication=[])
92+ self.assertThat(resource.is_authentication_attempted, Is(False))
93+
94+ def test_authenticated_is_False_when_authentication_is_NoAuthn(self):
95+ resource = OperationsResource(
96+ StubHandler, authentication=NoAuthentication())
97+ self.assertThat(resource.is_authentication_attempted, Is(False))
98+
99+ def test_authenticated_is_True_when_authentication_is_provided(self):
100+ resource = OperationsResource(
101+ StubHandler, authentication=sentinel.authentication)
102+ self.assertThat(resource.is_authentication_attempted, Is(True))
103+
104+
105+class TestRestrictedResources(APITestCase):
106+ """Tests for `RestrictedResource` and `AdminRestrictedResource`."""
107+
108+ scenarios = (
109+ ("user", dict(resource_type=RestrictedResource)),
110+ ("admin", dict(resource_type=AdminRestrictedResource)),
111+ )
112+
113+ def test_authentication_must_not_be_None(self):
114+ error = self.assertRaises(
115+ AssertionError, self.resource_type, StubHandler,
116+ authentication=None)
117+ self.assertThat(unicode(error), Equals(
118+ "Authentication must be attempted."))
119+
120+ def test_authentication_must_be_non_empty(self):
121+ error = self.assertRaises(
122+ AssertionError, self.resource_type, StubHandler,
123+ authentication=[])
124+ self.assertThat(unicode(error), Equals(
125+ "Authentication must be attempted."))
126+
127+ def test_authentication_must_be_meaningful(self):
128+ error = self.assertRaises(
129+ AssertionError, self.resource_type, StubHandler,
130+ authentication=NoAuthentication())
131+ self.assertThat(unicode(error), Equals(
132+ "Authentication must be attempted."))
133+
134+ def test_authentication_is_okay(self):
135+ resource = self.resource_type(
136+ StubHandler, authentication=sentinel.authentication)
137+ self.assertThat(resource.is_authentication_attempted, Is(True))
138+
139
140 class TestAdminMethodDecorator(APITestCase):
141
142
143=== modified file 'src/maasserver/api/tests/test_version.py'
144--- src/maasserver/api/tests/test_version.py 2015-06-24 14:20:57 +0000
145+++ src/maasserver/api/tests/test_version.py 2016-05-26 14:12:20 +0000
146@@ -1,4 +1,4 @@
147-# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
148+# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
149 # GNU Affero General Public License version 3 (see the file LICENSE).
150
151 """Test maasserver API version."""
152@@ -18,19 +18,31 @@
153 import httplib
154 import json
155
156+from django.contrib.auth.models import AnonymousUser
157 from django.core.urlresolvers import reverse
158 from maasserver.api.version import API_CAPABILITIES_LIST
159+from maasserver.testing.api import MultipleUsersScenarios
160+from maasserver.testing.factory import factory
161 from maasserver.testing.testcase import MAASServerTestCase
162 from maasserver.utils import version as version_module
163
164
165-class TestFindingResources(MAASServerTestCase):
166- """Tests for /version/ API."""
167+class TestVersionAPIBasics(MAASServerTestCase):
168+ """Basic tests for /version/ API."""
169
170 def test_handler_path(self):
171 self.assertEqual(
172 '/api/1.0/version/', reverse('version_handler'))
173
174+
175+class TestVersionAPI(MultipleUsersScenarios, MAASServerTestCase):
176+ """Tests for /version/ API."""
177+
178+ scenarios = [
179+ ('anon', dict(userfactory=AnonymousUser)),
180+ ('user', dict(userfactory=factory.make_User)),
181+ ]
182+
183 def test_GET_returns_details(self):
184 mock_apt = self.patch(version_module, "get_version_from_apt")
185 mock_apt.return_value = "1.8.0~alpha4+bzr356-0ubuntu1"
186
187=== modified file 'src/maasserver/urls_api.py'
188--- src/maasserver/urls_api.py 2015-12-07 14:25:30 +0000
189+++ src/maasserver/urls_api.py 2016-05-26 14:12:20 +0000
190@@ -132,6 +132,7 @@
191 )
192 from maasserver.api.support import (
193 AdminRestrictedResource,
194+ OperationsResource,
195 RestrictedResource,
196 )
197 from maasserver.api.tags import (
198@@ -219,7 +220,7 @@
199 BootImagesHandler, authentication=api_auth)
200 tag_handler = RestrictedResource(TagHandler, authentication=api_auth)
201 tags_handler = RestrictedResource(TagsHandler, authentication=api_auth)
202-version_handler = RestrictedResource(VersionHandler)
203+version_handler = OperationsResource(VersionHandler)
204 node_results_handler = RestrictedResource(
205 NodeResultsHandler, authentication=api_auth)
206 sshkey_handler = RestrictedResource(SSHKeyHandler, authentication=api_auth)

Subscribers

People subscribed via source and target branches