Merge ~ack/maas:1812402-create-device-rbac into maas:master

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: c0ed2cdc00f525eeb66ffaafdf9a165b8ba2fb1a
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:1812402-create-device-rbac
Merge into: maas:master
Diff against target: 137 lines (+55/-1)
6 files modified
src/maasserver/api/devices.py (+3/-0)
src/maasserver/api/tests/test_devices.py (+9/-0)
src/maasserver/forms/__init__.py (+5/-0)
src/maasserver/forms/tests/test_device.py (+32/-0)
src/maasserver/websockets/base.py (+3/-0)
src/maasserver/websockets/handlers/tests/test_device.py (+3/-1)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+367985@code.launchpad.net

Commit message

LP: #1812402 - check if a user is allowed to create devices when RBAC is used

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

This really should be done where all other permissions are done. Inside the user.has_perm().

We could add a new NodePermission or a specific DevicePermission. But I think placing this in the form, seperate from the other permission checks is the wrong place.

The permission check logic is correct, I just don't like the location of the check itself. So could it be moved to to inside the user.has_perm(DevicePermission.create) or something like that.

review: Needs Fixing
Revision history for this message
Alberto Donato (ack) wrote :

do you mean MAASAuthorizationBackend.has_perm ? that's where other checks are, and that logic exists there (that's where I copied it from) but the form doesn't go through that flow it seems

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

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

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/devices.py b/src/maasserver/api/devices.py
2index 89df68b..65a2423 100644
3--- a/src/maasserver/api/devices.py
4+++ b/src/maasserver/api/devices.py
5@@ -6,6 +6,7 @@ __all__ = [
6 "DevicesHandler",
7 ]
8
9+from django.core.exceptions import PermissionDenied
10 from maasserver.api.interfaces import DISPLAYED_INTERFACE_FIELDS
11 from maasserver.api.logger import maaslog
12 from maasserver.api.nodes import (
13@@ -234,6 +235,8 @@ class DevicesHandler(NodesHandler):
14 parameters.
15 """
16 form = DeviceWithMACsForm(data=request.data, request=request)
17+ if not form.has_perm(request.user):
18+ raise PermissionDenied()
19 if form.is_valid():
20 device = form.save()
21 parent = device.parent
22diff --git a/src/maasserver/api/tests/test_devices.py b/src/maasserver/api/tests/test_devices.py
23index 117a13a..9b34672 100644
24--- a/src/maasserver/api/tests/test_devices.py
25+++ b/src/maasserver/api/tests/test_devices.py
26@@ -268,6 +268,15 @@ class TestDevicesAPI(APITestCase.ForUser):
27 ],
28 list(parsed_result[0]))
29
30+ def test_create_no_permission(self):
31+ self.patch(auth, 'validate_user_external_auth').return_value = True
32+ self.useFixture(RBACEnabled())
33+ self.become_non_local()
34+ response = self.client.post(
35+ reverse('devices_handler'),
36+ {'mac_addresses': ['aa:bb:cc:dd:ee:ff']})
37+ self.assertEqual(response.status_code, http.client.FORBIDDEN)
38+
39
40 def get_device_uri(device):
41 """Return a device's URI on the API."""
42diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py
43index 419fcfa..48cd078 100644
44--- a/src/maasserver/forms/__init__.py
45+++ b/src/maasserver/forms/__init__.py
46@@ -944,6 +944,11 @@ class DeviceForm(NodeForm):
47 if instance is not None:
48 self.initial['zone'] = instance.zone.name
49
50+ def has_perm(self, user):
51+ # see MAASAuthorizationBackend.has_perm for the logic behind the
52+ # permission check
53+ return user.has_perm(NodePermission.view)
54+
55 def save(self, commit=True):
56 device = super(DeviceForm, self).save(commit=False)
57 device.node_type = NODE_TYPE.DEVICE
58diff --git a/src/maasserver/forms/tests/test_device.py b/src/maasserver/forms/tests/test_device.py
59index 071bc08..79e2409 100644
60--- a/src/maasserver/forms/tests/test_device.py
61+++ b/src/maasserver/forms/tests/test_device.py
62@@ -16,6 +16,7 @@ from maasserver.models import (
63 Interface,
64 )
65 from maasserver.testing.factory import factory
66+from maasserver.testing.fixtures import RBACEnabled
67 from maasserver.testing.testcase import MAASServerTestCase
68 from maasserver.utils.forms import get_QueryDict
69 from maasserver.utils.orm import (
70@@ -59,6 +60,37 @@ class TestDeviceForm(MAASServerTestCase):
71
72 self.assertEqual(parent, device.parent)
73
74+ def test_has_perm_no_rbac(self):
75+ form = DeviceForm()
76+ self.assertTrue(form.has_perm(factory.make_User()))
77+
78+ def test_has_perm_rbac_no_permision(self):
79+ self.useFixture(RBACEnabled())
80+ form = DeviceForm()
81+ self.assertFalse(form.has_perm(factory.make_User()))
82+
83+ def test_has_perm_rbac_global_admin(self):
84+ self.useFixture(RBACEnabled())
85+ user = factory.make_admin()
86+ form = DeviceForm()
87+ self.assertTrue(form.has_perm(user))
88+
89+ def test_has_perm_rbac_permission_on_pool(self):
90+ rbac = self.useFixture(RBACEnabled())
91+ user = factory.make_User()
92+ rbac.store.allow(
93+ user.username, factory.make_ResourcePool(), 'admin-machines')
94+ form = DeviceForm()
95+ self.assertTrue(form.has_perm(user))
96+
97+ def test_has_perm_rbac_read_permission_on_pool(self):
98+ rbac = self.useFixture(RBACEnabled())
99+ user = factory.make_User()
100+ rbac.store.allow(
101+ user.username, factory.make_ResourcePool(), 'view')
102+ form = DeviceForm()
103+ self.assertFalse(form.has_perm(user))
104+
105
106 class TestDeviceWithMACsForm(MAASServerTestCase):
107
108diff --git a/src/maasserver/websockets/base.py b/src/maasserver/websockets/base.py
109index 1e8baab..6060a44 100644
110--- a/src/maasserver/websockets/base.py
111+++ b/src/maasserver/websockets/base.py
112@@ -64,6 +64,9 @@ class HandlerDoesNotExistError(HandlerError):
113 class HandlerPermissionError(HandlerError):
114 """Raised when permission is denied for the user of a given action."""
115
116+ def __init__(self):
117+ super().__init__("Permission denied")
118+
119
120 class HandlerOptions(object):
121 """Configuraton class for `Handler`.
122diff --git a/src/maasserver/websockets/handlers/tests/test_device.py b/src/maasserver/websockets/handlers/tests/test_device.py
123index 0ca33af..9f74a44 100644
124--- a/src/maasserver/websockets/handlers/tests/test_device.py
125+++ b/src/maasserver/websockets/handlers/tests/test_device.py
126@@ -1025,8 +1025,10 @@ class TestDeviceHandler(MAASTransactionServerTestCase):
127
128 @transactional
129 def test_update_owned_with_rbac(self):
130- self.useFixture(RBACEnabled())
131+ rbac = self.useFixture(RBACEnabled())
132 user = factory.make_User(is_local=False)
133+ rbac.store.allow(
134+ user.username, factory.make_ResourcePool(), 'admin-machines')
135 node = factory.make_Node(owner=user, node_type=NODE_TYPE.DEVICE)
136 handler = DeviceHandler(user, {}, None)
137 new_hostname = factory.make_name("hostname")

Subscribers

People subscribed via source and target branches