launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #12499
[Merge] lp:~jameinel/maas/tags-exposing-nodes into lp:maas
John A Meinel has proposed merging lp:~jameinel/maas/tags-exposing-nodes into lp:maas.
Commit message:
/tag/<tagname>?op=nodes no longer exposes private nodes.
Respect that if there is a node owner, it doesn't get shown to anyone but superusers and that owner.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jameinel/maas/tags-exposing-nodes/+merge/126438
See commit message.
--
https://code.launchpad.net/~jameinel/maas/tags-exposing-nodes/+merge/126438
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jameinel/maas/tags-exposing-nodes into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-09-26 08:54:29 +0000
+++ src/maasserver/api.py 2012-09-26 12:10:28 +0000
@@ -1303,10 +1303,7 @@
@api_exported('POST')
def nodes(self, request, name):
"""Get the list of nodes that have this tag."""
- tag = Tag.objects.get_tag_or_404(name=name, user=request.user)
- # XXX: JAM 2012-09-25 We need to filter the node set returned by the
- # visibility defined by the user.
- return tag.node_set.all()
+ return Tag.objects.get_nodes(name, user=request.user)
@classmethod
def resource_uri(cls, tag=None):
=== modified file 'src/maasserver/models/tag.py'
--- src/maasserver/models/tag.py 2012-09-23 14:50:34 +0000
+++ src/maasserver/models/tag.py 2012-09-26 12:10:28 +0000
@@ -21,6 +21,7 @@
CharField,
Manager,
TextField,
+ Q,
)
from django.shortcuts import get_object_or_404
from maasserver import DefaultMeta
@@ -55,6 +56,23 @@
tag = get_object_or_404(Tag, name=name)
return tag
+ def get_nodes(self, tag_name, user):
+ """Get the list of nodes that have this tag.
+
+ This list is restricted to only nodes that the user has VIEW permission
+ for.
+ """
+ tag = self.get_tag_or_404(name=tag_name, user=user)
+ # The privacy logic is taken from Node. Note that we could filter in
+ # python by iterating over all nodes and checking
+ # user.has_perm(VIEW, node)
+ # It seems better to do this in the DB, though.
+ if user.is_superuser:
+ return tag.node_set.all()
+ else:
+ return tag.node_set.filter(
+ Q(owner__isnull=True) | Q(owner=user))
+
class Tag(CleanSave, TimestampedModel):
"""A `Tag` is a label applied to a `Node`.
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-09-26 08:54:29 +0000
+++ src/maasserver/tests/test_api.py 2012-09-26 12:10:28 +0000
@@ -2275,6 +2275,28 @@
self.assertEqual([node1.system_id],
[r['system_id'] for r in parsed_result])
+ def test_POST_nodes_hides_invisible_nodes(self):
+ user2 = factory.make_user()
+ node1 = factory.make_node()
+ node1.set_hardware_details('<node><foo /></node>')
+ node2 = factory.make_node(status=NODE_STATUS.ALLOCATED, owner=user2)
+ node2.set_hardware_details('<node><bar /></node>')
+ tag = factory.make_tag(definition='/node')
+ tag.populate_nodes()
+ response = self.client.post(self.get_tag_uri(tag), {'op': 'nodes'})
+
+ self.assertEqual(httplib.OK, response.status_code)
+ parsed_result = json.loads(response.content)
+ self.assertEqual([node1.system_id],
+ [r['system_id'] for r in parsed_result])
+ # However, for the other user, they should see the result
+ client2 = OAuthAuthenticatedClient(user2)
+ response = client2.post(self.get_tag_uri(tag), {'op': 'nodes'})
+ self.assertEqual(httplib.OK, response.status_code)
+ parsed_result = json.loads(response.content)
+ self.assertItemsEqual([node1.system_id, node2.system_id],
+ [r['system_id'] for r in parsed_result])
+
def test_PUT_invalid_definition(self):
self.become_admin()
node = factory.make_node()
=== modified file 'src/maasserver/tests/test_tag.py'
--- src/maasserver/tests/test_tag.py 2012-09-26 08:40:33 +0000
+++ src/maasserver/tests/test_tag.py 2012-09-26 12:10:28 +0000
@@ -15,6 +15,8 @@
from django.db import transaction
from django.db.utils import DatabaseError
from maastesting.djangotestcase import TransactionTestCase
+from maasserver.enum import NODE_STATUS
+from maasserver.models import Tag
from maasserver.testing.factory import factory
from maasserver.testing.testcase import TestCase
@@ -79,6 +81,39 @@
self.assertItemsEqual([tag1.name], node1.tag_names())
self.assertItemsEqual([tag2.name], node2.tag_names())
+ def test_get_nodes_returns_unowned_nodes(self):
+ user1 = factory.make_user()
+ node1 = factory.make_node()
+ tag = factory.make_tag()
+ node1.tags.add(tag)
+ self.assertItemsEqual([node1], Tag.objects.get_nodes(tag.name, user1))
+
+ def test_get_nodes_returns_self_owned_nodes(self):
+ user1 = factory.make_user()
+ node1 = factory.make_node(owner=user1)
+ tag = factory.make_tag()
+ node1.tags.add(tag)
+ self.assertItemsEqual([node1], Tag.objects.get_nodes(tag.name, user1))
+
+ def test_get_nodes_doesnt_return_other_owned_nodes(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ node1 = factory.make_node(owner=user1)
+ tag = factory.make_tag()
+ node1.tags.add(tag)
+ self.assertItemsEqual([], Tag.objects.get_nodes(tag.name, user2))
+
+ def test_get_nodes_returns_everything_for_superuser(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ node1 = factory.make_node(owner=user1)
+ node2 = factory.make_node()
+ tag = factory.make_tag()
+ node1.tags.add(tag)
+ node2.tags.add(tag)
+ self.assertItemsEqual([node1, node2],
+ Tag.objects.get_nodes(tag.name, user2))
+
class TestTagTransactions(TransactionTestCase):