summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-07-31 08:51:12 +0000
committerGerrit Code Review <review@openstack.org>2018-07-31 08:51:12 +0000
commit08a61b6f106918f78b046a5a5182e93a4e3e3dfd (patch)
tree51e06772b0bcc6806d50a25dfcdc5b3d128014ad
parentfc11871b8676c58cb0ed8f76027723450a48f1ca (diff)
parent522f38870916e6d2ddbe07b040e96b1fbe85595d (diff)
Merge "Add a policy to control the right to publish resources"
-rw-r--r--mistral/api/controllers/v2/action.py4
-rw-r--r--mistral/api/controllers/v2/workflow.py4
-rw-r--r--mistral/policies/action.py15
-rw-r--r--mistral/policies/workflow.py15
-rw-r--r--mistral/tests/unit/api/test_policies.py78
-rw-r--r--mistral/tests/unit/policies/__init__.py0
-rw-r--r--mistral/tests/unit/policies/test_actions.py249
-rw-r--r--mistral/tests/unit/policies/test_workflows.py279
-rw-r--r--releasenotes/notes/add-publicize-policy-d3b44590286c7fdd.yaml7
9 files changed, 573 insertions, 78 deletions
diff --git a/mistral/api/controllers/v2/action.py b/mistral/api/controllers/v2/action.py
index 0f94ef8..6601897 100644
--- a/mistral/api/controllers/v2/action.py
+++ b/mistral/api/controllers/v2/action.py
@@ -83,6 +83,8 @@ class ActionsController(rest.RestController, hooks.HookController):
83 83
84 scope = pecan.request.GET.get('scope', 'private') 84 scope = pecan.request.GET.get('scope', 'private')
85 resources.Action.validate_scope(scope) 85 resources.Action.validate_scope(scope)
86 if scope == 'public':
87 acl.enforce('actions:publicize', context.ctx())
86 88
87 @rest_utils.rest_retry_on_db_error 89 @rest_utils.rest_retry_on_db_error
88 def _update_actions(): 90 def _update_actions():
@@ -116,6 +118,8 @@ class ActionsController(rest.RestController, hooks.HookController):
116 pecan.response.status = 201 118 pecan.response.status = 201
117 119
118 resources.Action.validate_scope(scope) 120 resources.Action.validate_scope(scope)
121 if scope == 'public':
122 acl.enforce('actions:publicize', context.ctx())
119 123
120 LOG.debug("Create action(s) [definition=%s]", definition) 124 LOG.debug("Create action(s) [definition=%s]", definition)
121 125
diff --git a/mistral/api/controllers/v2/workflow.py b/mistral/api/controllers/v2/workflow.py
index 0186367..4134b97 100644
--- a/mistral/api/controllers/v2/workflow.py
+++ b/mistral/api/controllers/v2/workflow.py
@@ -117,6 +117,8 @@ class WorkflowsController(rest.RestController, hooks.HookController):
117 scope = pecan.request.GET.get('scope', 'private') 117 scope = pecan.request.GET.get('scope', 'private')
118 118
119 resources.Workflow.validate_scope(scope) 119 resources.Workflow.validate_scope(scope)
120 if scope == 'public':
121 acl.enforce('workflows:publicize', context.ctx())
120 122
121 LOG.debug("Update workflow(s) [definition=%s]", definition) 123 LOG.debug("Update workflow(s) [definition=%s]", definition)
122 124
@@ -153,6 +155,8 @@ class WorkflowsController(rest.RestController, hooks.HookController):
153 pecan.response.status = 201 155 pecan.response.status = 201
154 156
155 resources.Workflow.validate_scope(scope) 157 resources.Workflow.validate_scope(scope)
158 if scope == 'public':
159 acl.enforce('workflows:publicize', context.ctx())
156 160
157 LOG.debug("Create workflow(s) [definition=%s]", definition) 161 LOG.debug("Create workflow(s) [definition=%s]", definition)
158 162
diff --git a/mistral/policies/action.py b/mistral/policies/action.py
index c6eb91d..e5ddf9b 100644
--- a/mistral/policies/action.py
+++ b/mistral/policies/action.py
@@ -63,6 +63,21 @@ rules = [
63 ] 63 ]
64 ), 64 ),
65 policy.DocumentedRuleDefault( 65 policy.DocumentedRuleDefault(
66 name=ACTIONS % 'publicize',
67 check_str=base.RULE_ADMIN_OR_OWNER,
68 description='Make an action publicly available',
69 operations=[
70 {
71 'path': '/v2/actions',
72 'method': 'POST'
73 },
74 {
75 'path': '/v2/actions',
76 'method': 'PUT'
77 }
78 ]
79 ),
80 policy.DocumentedRuleDefault(
66 name=ACTIONS % 'update', 81 name=ACTIONS % 'update',
67 check_str=base.RULE_ADMIN_OR_OWNER, 82 check_str=base.RULE_ADMIN_OR_OWNER,
68 description='Update one or more actions.', 83 description='Update one or more actions.',
diff --git a/mistral/policies/workflow.py b/mistral/policies/workflow.py
index 161e585..5f1651d 100644
--- a/mistral/policies/workflow.py
+++ b/mistral/policies/workflow.py
@@ -74,6 +74,21 @@ rules = [
74 ] 74 ]
75 ), 75 ),
76 policy.DocumentedRuleDefault( 76 policy.DocumentedRuleDefault(
77 name=WORKFLOWS % 'publicize',
78 check_str=base.RULE_ADMIN_OR_OWNER,
79 description='Make a workflow publicly available',
80 operations=[
81 {
82 'path': '/v2/workflows',
83 'method': 'POST'
84 },
85 {
86 'path': '/v2/workflows',
87 'method': 'PUT'
88 }
89 ]
90 ),
91 policy.DocumentedRuleDefault(
77 name=WORKFLOWS % 'update', 92 name=WORKFLOWS % 'update',
78 check_str=base.RULE_ADMIN_OR_OWNER, 93 check_str=base.RULE_ADMIN_OR_OWNER,
79 description='Update one or more workflows.', 94 description='Update one or more workflows.',
diff --git a/mistral/tests/unit/api/test_policies.py b/mistral/tests/unit/api/test_policies.py
deleted file mode 100644
index db82114..0000000
--- a/mistral/tests/unit/api/test_policies.py
+++ /dev/null
@@ -1,78 +0,0 @@
1# Copyright 2016 NEC Corporation. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15
16import datetime
17
18import mock
19
20from mistral.db.v2 import api as db_api
21from mistral.db.v2.sqlalchemy import models
22from mistral.tests.unit.api import base
23from mistral.tests.unit.mstrlfixtures import policy_fixtures
24
25WF_DEFINITION = """
26---
27version: '2.0'
28
29flow:
30 type: direct
31 input:
32 - param1
33
34 tasks:
35 task1:
36 action: std.echo output="Hi"
37"""
38
39WF_DB = models.WorkflowDefinition(
40 id='123e4567-e89b-12d3-a456-426655440000',
41 name='flow',
42 definition=WF_DEFINITION,
43 created_at=datetime.datetime(1970, 1, 1),
44 updated_at=datetime.datetime(1970, 1, 1),
45 spec={'input': ['param1']}
46)
47
48WF = {
49 'id': '123e4567-e89b-12d3-a456-426655440000',
50 'name': 'flow',
51 'definition': WF_DEFINITION,
52 'created_at': '1970-01-01 00:00:00',
53 'updated_at': '1970-01-01 00:00:00',
54 'input': 'param1'
55}
56
57MOCK_WF = mock.MagicMock(return_value=WF_DB)
58
59
60class TestPolicies(base.APITest):
61 @mock.patch.object(db_api, "get_workflow_definition", MOCK_WF)
62 def get(self):
63 resp = self.app.get('/v2/workflows/123', expect_errors=True)
64 return resp.status_int
65
66 def test_disable_workflow_api(self):
67 self.policy = self.useFixture(policy_fixtures.PolicyFixture())
68 rules = {"workflows:get": "role:FAKE"}
69 self.policy.change_policy_definition(rules)
70 response_value = self.get()
71 self.assertEqual(403, response_value)
72
73 def test_enable_workflow_api(self):
74 self.policy = self.useFixture(policy_fixtures.PolicyFixture())
75 rules = {"workflows:get": "role:FAKE or rule:admin_or_owner"}
76 self.policy.change_policy_definition(rules)
77 response_value = self.get()
78 self.assertEqual(200, response_value)
diff --git a/mistral/tests/unit/policies/__init__.py b/mistral/tests/unit/policies/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mistral/tests/unit/policies/__init__.py
diff --git a/mistral/tests/unit/policies/test_actions.py b/mistral/tests/unit/policies/test_actions.py
new file mode 100644
index 0000000..53fff3f
--- /dev/null
+++ b/mistral/tests/unit/policies/test_actions.py
@@ -0,0 +1,249 @@
1# Copyright 2018 OVH SAS. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15
16import mock
17
18from mistral.db.v2 import api as db_api
19from mistral.db.v2.sqlalchemy import models
20from mistral.tests.unit.api import base
21from mistral.tests.unit.mstrlfixtures import policy_fixtures
22
23MOCK_DELETE = mock.MagicMock(return_value=None)
24
25ACTION_DEFINITION = """
26---
27version: '2.0'
28
29my_action:
30 description: My super cool action.
31 tags: ['test', 'v2']
32 base: std.echo
33 base-input:
34 output: "{$.str1}{$.str2}"
35"""
36ACTION_DB = models.ActionDefinition(
37 id='123e4567-e89b-12d3-a456-426655440000',
38 name='my_action',
39 is_system=False,
40 description='My super cool action.',
41 tags=['test', 'v2'],
42 definition=ACTION_DEFINITION
43)
44MOCK_ACTION = mock.MagicMock(return_value=ACTION_DB)
45
46
47class TestActionPolicy(base.APITest):
48 """Test action related policies
49
50 Policies to test:
51 - actions:create
52 - actions:delete
53 - actions:get
54 - actions:list
55 - actions:publicize (on POST & PUT)
56 - actions:update
57 """
58
59 def setUp(self):
60 self.policy = self.useFixture(policy_fixtures.PolicyFixture())
61 super(TestActionPolicy, self).setUp()
62
63 @mock.patch.object(db_api, "create_action_definition")
64 def test_action_create_not_allowed(self, mock_obj):
65 self.policy.change_policy_definition(
66 {"actions:create": "role:FAKE"}
67 )
68 resp = self.app.post(
69 '/v2/actions',
70 ACTION_DEFINITION,
71 headers={'Content-Type': 'text/plain'},
72 expect_errors=True
73 )
74
75 self.assertEqual(403, resp.status_int)
76
77 @mock.patch.object(db_api, "create_action_definition")
78 def test_action_create_allowed(self, mock_obj):
79 self.policy.change_policy_definition(
80 {"actions:create": "role:FAKE or rule:admin_or_owner"}
81 )
82 resp = self.app.post(
83 '/v2/actions',
84 ACTION_DEFINITION,
85 headers={'Content-Type': 'text/plain'},
86 expect_errors=True
87 )
88
89 self.assertEqual(201, resp.status_int)
90
91 @mock.patch.object(db_api, "create_action_definition")
92 def test_action_create_public_not_allowed(self, mock_obj):
93 self.policy.change_policy_definition({
94 "actions:create": "role:FAKE or rule:admin_or_owner",
95 "actions:publicize": "role:FAKE"
96 })
97 resp = self.app.post(
98 '/v2/actions?scope=public',
99 ACTION_DEFINITION,
100 headers={'Content-Type': 'text/plain'},
101 expect_errors=True
102 )
103
104 self.assertEqual(403, resp.status_int)
105
106 @mock.patch.object(db_api, "create_action_definition")
107 def test_action_create_public_allowed(self, mock_obj):
108 self.policy.change_policy_definition({
109 "actions:create": "role:FAKE or rule:admin_or_owner",
110 "actions:publicize": "role:FAKE or rule:admin_or_owner"
111 })
112 resp = self.app.post(
113 '/v2/actions?scope=public',
114 ACTION_DEFINITION,
115 headers={'Content-Type': 'text/plain'},
116 expect_errors=True
117 )
118
119 self.assertEqual(201, resp.status_int)
120
121 @mock.patch.object(db_api, "delete_action_definition", MOCK_DELETE)
122 @mock.patch.object(db_api, "get_action_definition", MOCK_ACTION)
123 def test_action_delete_not_allowed(self):
124 self.policy.change_policy_definition(
125 {"actions:delete": "role:FAKE"}
126 )
127 resp = self.app.delete(
128 '/v2/actions/123',
129 expect_errors=True
130 )
131
132 self.assertEqual(403, resp.status_int)
133
134 @mock.patch.object(db_api, "delete_action_definition", MOCK_DELETE)
135 @mock.patch.object(db_api, "get_action_definition", MOCK_ACTION)
136 def test_action_delete_allowed(self):
137 self.policy.change_policy_definition(
138 {"actions:delete": "role:FAKE or rule:admin_or_owner"}
139 )
140 resp = self.app.delete(
141 '/v2/actions/123',
142 expect_errors=True
143 )
144
145 self.assertEqual(204, resp.status_int)
146
147 @mock.patch.object(db_api, "get_action_definition", MOCK_ACTION)
148 def test_action_get_not_allowed(self):
149 self.policy.change_policy_definition(
150 {"actions:get": "role:FAKE"}
151 )
152 resp = self.app.get(
153 '/v2/actions/123',
154 expect_errors=True
155 )
156
157 self.assertEqual(403, resp.status_int)
158
159 @mock.patch.object(db_api, "get_action_definition", MOCK_ACTION)
160 def test_action_get_allowed(self):
161 self.policy.change_policy_definition(
162 {"actions:get": "role:FAKE or rule:admin_or_owner"}
163 )
164 resp = self.app.get(
165 '/v2/actions/123',
166 expect_errors=True
167 )
168
169 self.assertEqual(200, resp.status_int)
170
171 def test_action_list_not_allowed(self):
172 self.policy.change_policy_definition(
173 {"actions:list": "role:FAKE"}
174 )
175 resp = self.app.get(
176 '/v2/actions',
177 expect_errors=True
178 )
179
180 self.assertEqual(403, resp.status_int)
181
182 def test_action_list_allowed(self):
183 self.policy.change_policy_definition(
184 {"actions:list": "role:FAKE or rule:admin_or_owner"}
185 )
186 resp = self.app.get(
187 '/v2/actions',
188 expect_errors=True
189 )
190
191 self.assertEqual(200, resp.status_int)
192
193 @mock.patch.object(db_api, "update_action_definition")
194 def test_action_update_not_allowed(self, mock_obj):
195 self.policy.change_policy_definition(
196 {"actions:update": "role:FAKE"}
197 )
198 resp = self.app.put(
199 '/v2/actions',
200 ACTION_DEFINITION,
201 headers={'Content-Type': 'text/plain'},
202 expect_errors=True
203 )
204
205 self.assertEqual(403, resp.status_int)
206
207 @mock.patch.object(db_api, "update_action_definition")
208 def test_action_update_allowed(self, mock_obj):
209 self.policy.change_policy_definition(
210 {"actions:update": "role:FAKE or rule:admin_or_owner"}
211 )
212 resp = self.app.put(
213 '/v2/actions',
214 ACTION_DEFINITION,
215 headers={'Content-Type': 'text/plain'},
216 expect_errors=True
217 )
218
219 self.assertEqual(200, resp.status_int)
220
221 @mock.patch.object(db_api, "update_action_definition")
222 def test_action_update_public_not_allowed(self, mock_obj):
223 self.policy.change_policy_definition({
224 "actions:update": "role:FAKE or rule:admin_or_owner",
225 "actions:publicize": "role:FAKE"
226 })
227 resp = self.app.put(
228 '/v2/actions?scope=public',
229 ACTION_DEFINITION,
230 headers={'Content-Type': 'text/plain'},
231 expect_errors=True
232 )
233
234 self.assertEqual(403, resp.status_int)
235
236 @mock.patch.object(db_api, "update_action_definition")
237 def test_action_update_public_allowed(self, mock_obj):
238 self.policy.change_policy_definition({
239 "actions:update": "role:FAKE or rule:admin_or_owner",
240 "actions:publicize": "role:FAKE or rule:admin_or_owner"
241 })
242 resp = self.app.put(
243 '/v2/actions?scope=public',
244 ACTION_DEFINITION,
245 headers={'Content-Type': 'text/plain'},
246 expect_errors=True
247 )
248
249 self.assertEqual(200, resp.status_int)
diff --git a/mistral/tests/unit/policies/test_workflows.py b/mistral/tests/unit/policies/test_workflows.py
new file mode 100644
index 0000000..71a3c8a
--- /dev/null
+++ b/mistral/tests/unit/policies/test_workflows.py
@@ -0,0 +1,279 @@
1# Copyright 2016 NEC Corporation. All rights reserved.
2# Copyright 2018 OVH SAS. All rights reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16
17import datetime
18
19import mock
20
21from mistral.db.v2 import api as db_api
22from mistral.db.v2.sqlalchemy import models
23from mistral.tests.unit.api import base
24from mistral.tests.unit.mstrlfixtures import policy_fixtures
25
26MOCK_DELETE = mock.MagicMock(return_value=None)
27
28WF_DEFINITION = """
29---
30version: '2.0'
31
32flow:
33 type: direct
34 input:
35 - param1
36
37 tasks:
38 task1:
39 action: std.echo output="Hi"
40"""
41WF_DB = models.WorkflowDefinition(
42 id='123e4567-e89b-12d3-a456-426655440000',
43 name='flow',
44 definition=WF_DEFINITION,
45 created_at=datetime.datetime(1970, 1, 1),
46 updated_at=datetime.datetime(1970, 1, 1),
47 spec={'input': ['param1']}
48)
49MOCK_WF = mock.MagicMock(return_value=WF_DB)
50
51
52class TestWorkflowPolicy(base.APITest):
53 """Test workflow related policies
54
55 Policies to test:
56 - workflows:create
57 - workflows:delete
58 - workflows:get
59 - workflows:list
60 - workflows:list:all_projects
61 - workflows:publicize (on POST & PUT)
62 - workflows:update
63 """
64
65 def setUp(self):
66 self.policy = self.useFixture(policy_fixtures.PolicyFixture())
67 super(TestWorkflowPolicy, self).setUp()
68
69 @mock.patch.object(db_api, "create_workflow_definition")
70 def test_workflow_create_not_allowed(self, mock_obj):
71 self.policy.change_policy_definition(
72 {"workflows:create": "role:FAKE"}
73 )
74 resp = self.app.post(
75 '/v2/workflows',
76 WF_DEFINITION,
77 headers={'Content-Type': 'text/plain'},
78 expect_errors=True
79 )
80
81 self.assertEqual(403, resp.status_int)
82
83 @mock.patch.object(db_api, "create_workflow_definition")
84 def test_workflow_create_allowed(self, mock_obj):
85 self.policy.change_policy_definition(
86 {"workflows:create": "role:FAKE or rule:admin_or_owner"}
87 )
88 resp = self.app.post(
89 '/v2/workflows',
90 WF_DEFINITION,
91 headers={'Content-Type': 'text/plain'},
92 expect_errors=True
93 )
94
95 self.assertEqual(201, resp.status_int)
96
97 @mock.patch.object(db_api, "create_workflow_definition")
98 def test_workflow_create_public_not_allowed(self, mock_obj):
99 self.policy.change_policy_definition({
100 "workflows:create": "role:FAKE or rule:admin_or_owner",
101 "workflows:publicize": "role:FAKE"
102 })
103 resp = self.app.post(
104 '/v2/workflows?scope=public',
105 WF_DEFINITION,
106 headers={'Content-Type': 'text/plain'},
107 expect_errors=True
108 )
109
110 self.assertEqual(403, resp.status_int)
111
112 @mock.patch.object(db_api, "create_workflow_definition")
113 def test_workflow_create_public_allowed(self, mock_obj):
114 self.policy.change_policy_definition({
115 "workflows:create": "role:FAKE or rule:admin_or_owner",
116 "workflows:publicize": "role:FAKE or rule:admin_or_owner"
117 })
118 resp = self.app.post(
119 '/v2/workflows?scope=public',
120 WF_DEFINITION,
121 headers={'Content-Type': 'text/plain'},
122 expect_errors=True
123 )
124
125 self.assertEqual(201, resp.status_int)
126
127 @mock.patch.object(db_api, "delete_workflow_definition", MOCK_DELETE)
128 @mock.patch.object(db_api, "get_workflow_definition", MOCK_WF)
129 def test_workflow_delete_not_allowed(self):
130 self.policy.change_policy_definition(
131 {"workflows:delete": "role:FAKE"}
132 )
133 resp = self.app.delete(
134 '/v2/workflows/123',
135 expect_errors=True
136 )
137
138 self.assertEqual(403, resp.status_int)
139
140 @mock.patch.object(db_api, "delete_workflow_definition", MOCK_DELETE)
141 @mock.patch.object(db_api, "get_workflow_definition", MOCK_WF)
142 def test_workflow_delete_allowed(self):
143 self.policy.change_policy_definition(
144 {"workflows:delete": "role:FAKE or rule:admin_or_owner"}
145 )
146 resp = self.app.delete(
147 '/v2/workflows/123',
148 expect_errors=True
149 )
150
151 self.assertEqual(204, resp.status_int)
152
153 @mock.patch.object(db_api, "get_workflow_definition", MOCK_WF)
154 def test_workflow_get_not_allowed(self):
155 self.policy.change_policy_definition(
156 {"workflows:get": "role:FAKE"}
157 )
158 resp = self.app.get(
159 '/v2/workflows/123',
160 expect_errors=True
161 )
162
163 self.assertEqual(403, resp.status_int)
164
165 @mock.patch.object(db_api, "get_workflow_definition", MOCK_WF)
166 def test_workflow_get_allowed(self):
167 self.policy.change_policy_definition(
168 {"workflows:get": "role:FAKE or rule:admin_or_owner"}
169 )
170 resp = self.app.get(
171 '/v2/workflows/123',
172 expect_errors=True
173 )
174
175 self.assertEqual(200, resp.status_int)
176
177 def test_workflow_list_not_allowed(self):
178 self.policy.change_policy_definition(
179 {"workflows:list": "role:FAKE"}
180 )
181 resp = self.app.get(
182 '/v2/workflows',
183 expect_errors=True
184 )
185
186 self.assertEqual(403, resp.status_int)
187
188 def test_workflow_list_allowed(self):
189 self.policy.change_policy_definition(
190 {"workflows:list": "role:FAKE or rule:admin_or_owner"}
191 )
192 resp = self.app.get(
193 '/v2/workflows',
194 expect_errors=True
195 )
196
197 self.assertEqual(200, resp.status_int)
198
199 def test_workflow_list_all_not_allowed(self):
200 self.policy.change_policy_definition({
201 "workflows:list": "role:FAKE or rule:admin_or_owner",
202 "workflows:list:all_projects": "role:FAKE"
203 })
204 resp = self.app.get(
205 '/v2/workflows?all_projects=1',
206 expect_errors=True
207 )
208
209 self.assertEqual(403, resp.status_int)
210
211 def test_workflow_list_all_allowed(self):
212 self.policy.change_policy_definition({
213 "workflows:list": "role:FAKE or rule:admin_or_owner",
214 "workflows:list:all_projects": "role:FAKE or rule:admin_or_owner"
215 })
216 resp = self.app.get(
217 '/v2/workflows?all_projects=1',
218 expect_errors=True
219 )
220
221 self.assertEqual(200, resp.status_int)
222
223 @mock.patch.object(db_api, "update_workflow_definition")
224 def test_workflow_update_not_allowed(self, mock_obj):
225 self.policy.change_policy_definition(
226 {"workflows:update": "role:FAKE"}
227 )
228 resp = self.app.put(
229 '/v2/workflows',
230 WF_DEFINITION,
231 headers={'Content-Type': 'text/plain'},
232 expect_errors=True
233 )
234
235 self.assertEqual(403, resp.status_int)
236
237 @mock.patch.object(db_api, "update_workflow_definition")
238 def test_workflow_update_allowed(self, mock_obj):
239 self.policy.change_policy_definition(
240 {"workflows:update": "role:FAKE or rule:admin_or_owner"}
241 )
242 resp = self.app.put(
243 '/v2/workflows',
244 WF_DEFINITION,
245 headers={'Content-Type': 'text/plain'},
246 expect_errors=True
247 )
248
249 self.assertEqual(200, resp.status_int)
250
251 @mock.patch.object(db_api, "update_workflow_definition")
252 def test_workflow_update_public_not_allowed(self, mock_obj):
253 self.policy.change_policy_definition({
254 "workflows:update": "role:FAKE or rule:admin_or_owner",
255 "workflows:publicize": "role:FAKE"
256 })
257 resp = self.app.put(
258 '/v2/workflows?scope=public',
259 WF_DEFINITION,
260 headers={'Content-Type': 'text/plain'},
261 expect_errors=True
262 )
263
264 self.assertEqual(403, resp.status_int)
265
266 @mock.patch.object(db_api, "update_workflow_definition")
267 def test_workflow_update_public_allowed(self, mock_obj):
268 self.policy.change_policy_definition({
269 "workflows:update": "role:FAKE or rule:admin_or_owner",
270 "workflows:publicize": "role:FAKE or rule:admin_or_owner"
271 })
272 resp = self.app.put(
273 '/v2/workflows?scope=public',
274 WF_DEFINITION,
275 headers={'Content-Type': 'text/plain'},
276 expect_errors=True
277 )
278
279 self.assertEqual(200, resp.status_int)
diff --git a/releasenotes/notes/add-publicize-policy-d3b44590286c7fdd.yaml b/releasenotes/notes/add-publicize-policy-d3b44590286c7fdd.yaml
new file mode 100644
index 0000000..9fc7533
--- /dev/null
+++ b/releasenotes/notes/add-publicize-policy-d3b44590286c7fdd.yaml
@@ -0,0 +1,7 @@
1---
2features:
3 - |
4 Mistral now supports a `publicize` policy on actions and workflows which
5 controls whether the users are allowed to create or update them. The
6 default policy does not change which means that everyone can publish
7 action or workflow unless specified differently in the policy.