Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: appengine/config_service/api_test.py

Issue 1224913002: luci-config: fine-grained acls (Closed) Base URL: git@github.com:luci/luci-py.git@master
Patch Set: identities in configs Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « appengine/config_service/api.py ('k') | appengine/config_service/common.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2015 The Swarming Authors. All rights reserved. 2 # Copyright 2015 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed by the Apache v2.0 license that can be 3 # Use of this source code is governed by the Apache v2.0 license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 import base64 6 import base64
7 import copy 7 import copy
8 import httplib 8 import httplib
9 9
10 from test_env import future 10 from test_env import future
(...skipping 13 matching lines...) Expand all
24 import api 24 import api
25 import projects 25 import projects
26 import storage 26 import storage
27 27
28 28
29 class ApiTest(test_case.EndpointsTestCase): 29 class ApiTest(test_case.EndpointsTestCase):
30 api_service_cls = api.ConfigApi 30 api_service_cls = api.ConfigApi
31 31
32 def setUp(self): 32 def setUp(self):
33 super(ApiTest, self).setUp() 33 super(ApiTest, self).setUp()
34 self.mock(acl, 'has_project_access', mock.Mock(return_value=True)) 34 self.mock(acl, 'has_project_access', mock.Mock())
35 self.mock(acl, 'can_read_config_set', mock.Mock(return_value=True)) 35 acl.has_project_access.side_effect = (
36 self.mock(acl, 'can_read_project_config', mock.Mock(return_value=True)) 36 lambda pid: pid != 'secret'
37 )
38 self.mock(acl, 'has_service_access', mock.Mock(return_value=True))
37 self.mock(projects, 'get_projects', mock.Mock()) 39 self.mock(projects, 'get_projects', mock.Mock())
38 projects.get_projects.return_value = [ 40 projects.get_projects.return_value = [
39 service_config_pb2.Project(id='chromium'), 41 service_config_pb2.Project(id='chromium'),
40 service_config_pb2.Project(id='v8'), 42 service_config_pb2.Project(id='v8'),
41 ] 43 ]
42 self.mock(projects, 'get_metadata', mock.Mock()) 44 self.mock(projects, 'get_metadata', mock.Mock())
43 projects.get_metadata.return_value = project_config_pb2.ProjectCfg() 45 projects.get_metadata.return_value = project_config_pb2.ProjectCfg()
44 self.mock(projects, 'get_repo', mock.Mock()) 46 self.mock(projects, 'get_repo', mock.Mock())
45 projects.get_repo.return_value = ( 47 projects.get_repo.return_value = (
46 projects.RepositoryType.GITILES, 'https://localhost/project') 48 projects.RepositoryType.GITILES, 'https://localhost/project')
(...skipping 30 matching lines...) Expand all
77 self.assertEqual(resp, { 79 self.assertEqual(resp, {
78 'mappings': [ 80 'mappings': [
79 { 81 {
80 'config_set': 'services/x', 82 'config_set': 'services/x',
81 'location': 'http://x', 83 'location': 'http://x',
82 }, 84 },
83 ], 85 ],
84 }) 86 })
85 87
86 def test_get_config_one_forbidden(self): 88 def test_get_config_one_forbidden(self):
87 acl.can_read_config_set.return_value = False 89 self.mock(acl, 'can_read_config_set', mock.Mock(return_value=False))
88 with self.call_should_fail(httplib.FORBIDDEN): 90 with self.call_should_fail(httplib.FORBIDDEN):
89 req = { 91 req = {
90 'config_set': 'services/x', 92 'config_set': 'services/x',
91 } 93 }
92 self.call_api('get_mapping', req) 94 self.call_api('get_mapping', req)
93 95
94 def test_get_config_all(self): 96 def test_get_config_all(self):
95 self.mock(storage, 'get_mapping_async', mock.Mock()) 97 self.mock(storage, 'get_mapping_async', mock.Mock())
96 storage.get_mapping_async.return_value = future({ 98 storage.get_mapping_async.return_value = future({
97 'services/x': 'http://x', 99 'services/x': 'http://x',
(...skipping 14 matching lines...) Expand all
112 }, 114 },
113 ], 115 ],
114 }) 116 })
115 117
116 def test_get_config_all_partially_forbidden(self): 118 def test_get_config_all_partially_forbidden(self):
117 self.mock(storage, 'get_mapping_async', mock.Mock()) 119 self.mock(storage, 'get_mapping_async', mock.Mock())
118 storage.get_mapping_async.return_value = future({ 120 storage.get_mapping_async.return_value = future({
119 'services/x': 'http://x', 121 'services/x': 'http://x',
120 'services/y': 'http://y', 122 'services/y': 'http://y',
121 }) 123 })
122 acl.can_read_config_set.side_effect = [True, False] 124 self.mock(acl, 'can_read_config_set', mock.Mock(side_effect=[True, False]))
123 125
124 resp = self.call_api('get_mapping', {}).json_body 126 resp = self.call_api('get_mapping', {}).json_body
125 127
126 self.assertEqual(resp, { 128 self.assertEqual(resp, {
127 'mappings': [ 129 'mappings': [
128 { 130 {
129 'config_set': 'services/x', 131 'config_set': 'services/x',
130 'location': 'http://x', 132 'location': 'http://x',
131 }, 133 },
132 ], 134 ],
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
187 storage, 'get_config_hash_async', lambda *_, **__: future((None, None))) 189 storage, 'get_config_hash_async', lambda *_, **__: future((None, None)))
188 190
189 req = { 191 req = {
190 'config_set': 'services/x', 192 'config_set': 'services/x',
191 'path': 'a.cfg', 193 'path': 'a.cfg',
192 } 194 }
193 with self.call_should_fail(httplib.NOT_FOUND): 195 with self.call_should_fail(httplib.NOT_FOUND):
194 self.call_api('get_config', req) 196 self.call_api('get_config', req)
195 197
196 def test_get_wrong_config_set(self): 198 def test_get_wrong_config_set(self):
197 acl.can_read_config_set.side_effect = ValueError 199 self.mock(acl, 'can_read_config_set', mock.Mock(side_effect=ValueError))
198 200
199 req = { 201 req = {
200 'config_set': 'xxx', 202 'config_set': 'xxx',
201 'path': 'my.cfg', 203 'path': 'my.cfg',
202 'revision': 'deadbeef', 204 'revision': 'deadbeef',
203 } 205 }
204 with self.call_should_fail(httplib.BAD_REQUEST): 206 with self.call_should_fail(httplib.BAD_REQUEST):
205 self.call_api('get_config', req).json_body 207 self.call_api('get_config', req).json_body
206 208
207 def test_get_config_without_permissions(self): 209 def test_get_config_without_permissions(self):
208 acl.can_read_config_set.return_value = False 210 self.mock(acl, 'can_read_config_set', mock.Mock(return_value=False))
209 self.mock(storage, 'get_config_hash_async', mock.Mock()) 211 self.mock(storage, 'get_config_hash_async', mock.Mock())
210 212
211 req = { 213 req = {
212 'config_set': 'services/luci-config', 214 'config_set': 'services/luci-config',
213 'path': 'projects.cfg', 215 'path': 'projects.cfg',
214 } 216 }
215 with self.call_should_fail(httplib.NOT_FOUND): 217 with self.call_should_fail(404):
216 self.call_api('get_config', req) 218 self.call_api('get_config', req)
217 self.assertFalse(storage.get_config_hash_async.called) 219 self.assertFalse(storage.get_config_hash_async.called)
218 220
219 ############################################################################## 221 ##############################################################################
220 # get_config_by_hash 222 # get_config_by_hash
221 223
222 def test_get_config_by_hash(self): 224 def test_get_config_by_hash(self):
223 self.mock(storage, 'get_config_by_hash_async', mock.Mock()) 225 self.mock(storage, 'get_config_by_hash_async', mock.Mock())
224 storage.get_config_by_hash_async.return_value = future('some content') 226 storage.get_config_by_hash_async.return_value = future('some content')
225 227
226 req = {'content_hash': 'deadbeef'} 228 req = {'content_hash': 'deadbeef'}
227 resp = self.call_api('get_config_by_hash', req).json_body 229 resp = self.call_api('get_config_by_hash', req).json_body
228 230
229 self.assertEqual(resp, { 231 self.assertEqual(resp, {
230 'content': base64.b64encode('some content'), 232 'content': base64.b64encode('some content'),
231 }) 233 })
232 234
233 storage.get_config_by_hash_async.return_value = future(None) 235 storage.get_config_by_hash_async.return_value = future(None)
234 with self.call_should_fail(httplib.NOT_FOUND): 236 with self.call_should_fail(httplib.NOT_FOUND):
235 self.call_api('get_config_by_hash', req) 237 self.call_api('get_config_by_hash', req)
236 238
237 ############################################################################## 239 ##############################################################################
238 # get_projects 240 # get_projects
239 241
240 def test_get_projects(self): 242 def test_get_projects(self):
241 projects.get_projects.return_value = [ 243 projects.get_projects.return_value = [
242 service_config_pb2.Project(id='chromium'), 244 service_config_pb2.Project(id='chromium'),
243 service_config_pb2.Project(id='v8'), 245 service_config_pb2.Project(id='v8'),
244 service_config_pb2.Project(id='inconsistent'), 246 service_config_pb2.Project(id='inconsistent'),
247 service_config_pb2.Project(id='secret'),
245 ] 248 ]
246 projects.get_metadata.side_effect = [ 249 projects.get_metadata.side_effect = [
247 project_config_pb2.ProjectCfg(name='Chromium, the best browser'), 250 project_config_pb2.ProjectCfg(
248 project_config_pb2.ProjectCfg(), 251 name='Chromium, the best browser', access='all'),
249 project_config_pb2.ProjectCfg(), 252 project_config_pb2.ProjectCfg(access='all'),
253 project_config_pb2.ProjectCfg(access='all'),
254 project_config_pb2.ProjectCfg(access='administrators'),
250 ] 255 ]
251 projects.get_repo.side_effect = [ 256 projects.get_repo.side_effect = [
252 (projects.RepositoryType.GITILES, 'http://localhost/chromium'), 257 (projects.RepositoryType.GITILES, 'http://localhost/chromium'),
253 (projects.RepositoryType.GITILES, 'http://localhost/v8'), 258 (projects.RepositoryType.GITILES, 'http://localhost/v8'),
254 (None, None) 259 (None, None),
260 (projects.RepositoryType.GITILES, 'http://localhost/secret'),
255 ] 261 ]
256 262
257 resp = self.call_api('get_projects', {}).json_body 263 resp = self.call_api('get_projects', {}).json_body
258 264
259 self.assertEqual(resp, { 265 self.assertEqual(resp, {
260 'projects': [ 266 'projects': [
261 { 267 {
262 'id': 'chromium', 268 'id': 'chromium',
263 'name': 'Chromium, the best browser', 269 'name': 'Chromium, the best browser',
264 'repo_type': 'GITILES', 270 'repo_type': 'GITILES',
265 'repo_url': 'http://localhost/chromium', 271 'repo_url': 'http://localhost/chromium',
266 }, 272 },
267 { 273 {
268 'id': 'v8', 274 'id': 'v8',
269 'repo_type': 'GITILES', 275 'repo_type': 'GITILES',
270 'repo_url': 'http://localhost/v8', 276 'repo_url': 'http://localhost/v8',
271 }, 277 },
272 ], 278 ],
273 }) 279 })
274 280
275 def test_get_projects_without_permissions(self): 281 def test_get_projects_without_permissions(self):
276 acl.has_project_access.return_value = False 282 acl.has_project_access.return_value = False
277 with self.call_should_fail(httplib.FORBIDDEN): 283 self.call_api('get_projects', {})
278 self.call_api('get_projects', {})
279 284
280 ############################################################################## 285 ##############################################################################
281 # get_refs 286 # get_refs
282 287
283 def test_get_refs(self): 288 def test_get_refs(self):
284 self.mock_refs() 289 self.mock_refs()
285 290
286 req = {'project_id': 'chromium'} 291 req = {'project_id': 'chromium'}
287 resp = self.call_api('get_refs', req).json_body 292 resp = self.call_api('get_refs', req).json_body
288 293
289 self.assertEqual(resp, { 294 self.assertEqual(resp, {
290 'refs': [ 295 'refs': [
291 {'name': 'refs/heads/master'}, 296 {'name': 'refs/heads/master'},
292 {'name': 'refs/heads/release42'}, 297 {'name': 'refs/heads/release42'},
293 ], 298 ],
294 }) 299 })
295 300
296 def test_get_refs_without_permissions(self): 301 def test_get_refs_without_permissions(self):
297 self.mock_refs() 302 self.mock_refs()
298 acl.can_read_project_config.return_value = False 303 acl.has_project_access.side_effect = None
304 acl.has_project_access.return_value = False
299 305
300 req = {'project_id': 'chromium'} 306 req = {'project_id': 'chromium'}
301 with self.call_should_fail(httplib.NOT_FOUND): 307 with self.call_should_fail(httplib.NOT_FOUND):
302 self.call_api('get_refs', req) 308 self.call_api('get_refs', req)
303 self.assertFalse(projects.get_refs.called) 309 self.assertFalse(projects.get_refs.called)
304 310
305
306 def test_get_refs_of_non_existent_project(self): 311 def test_get_refs_of_non_existent_project(self):
307 self.mock(projects, 'get_refs', mock.Mock()) 312 self.mock(projects, 'get_refs', mock.Mock())
308 projects.get_refs.return_value = None 313 projects.get_refs.return_value = None
309 req = {'project_id': 'nonexistent'} 314 req = {'project_id': 'non-existent'}
310 with self.call_should_fail(httplib.NOT_FOUND): 315 with self.call_should_fail(httplib.NOT_FOUND):
311 self.call_api('get_refs', req) 316 self.call_api('get_refs', req)
312 317
313 ############################################################################## 318 ##############################################################################
314 # get_project_configs 319 # get_project_configs
315 320
316 def test_get_config_multi(self): 321 def test_get_config_multi(self):
317 self.mock_refs() 322 self.mock_refs()
323 projects.get_projects.return_value.extend([
324 service_config_pb2.Project(id='inconsistent'),
325 service_config_pb2.Project(id='secret'),
326 ])
318 327
319 self.mock(storage, 'get_latest_multi_async', mock.Mock()) 328 self.mock(storage, 'get_latest_multi_async', mock.Mock())
320 storage.get_latest_multi_async.return_value = future([ 329 storage.get_latest_multi_async.return_value = future([
321 { 330 {
322 'config_set': 'projects/chromium', 331 'config_set': 'projects/chromium',
323 'revision': 'deadbeef', 332 'revision': 'deadbeef',
324 'content_hash': 'abc0123', 333 'content_hash': 'abc0123',
325 'content': 'config text', 334 'content': 'config text',
326 }, 335 },
327 { 336 {
(...skipping 10 matching lines...) Expand all
338 self.assertEqual(resp, { 347 self.assertEqual(resp, {
339 'configs': [{ 348 'configs': [{
340 'config_set': 'projects/chromium', 349 'config_set': 'projects/chromium',
341 'revision': 'deadbeef', 350 'revision': 'deadbeef',
342 'content_hash': 'abc0123', 351 'content_hash': 'abc0123',
343 'content': base64.b64encode('config text'), 352 'content': base64.b64encode('config text'),
344 }], 353 }],
345 }) 354 })
346 config_sets_arg = storage.get_latest_multi_async.call_args[0][0] 355 config_sets_arg = storage.get_latest_multi_async.call_args[0][0]
347 self.assertEqual( 356 self.assertEqual(
348 list(config_sets_arg), ['projects/chromium', 'projects/v8']) 357 list(config_sets_arg),
349 358 ['projects/chromium', 'projects/v8', 'projects/inconsistent'])
350 def test_get_project_configs_without_permission(self):
351 self.mock(api, 'get_projects', mock.Mock())
352 acl.has_project_access.return_value = False
353
354 req = {'path': 'cq.cfg'}
355 with self.call_should_fail(httplib.FORBIDDEN):
356 self.call_api('get_project_configs', req)
357 self.assertFalse(api.get_projects.called)
358 359
359 ############################################################################## 360 ##############################################################################
360 # get_ref_configs 361 # get_ref_configs
361 362
362 def test_get_ref_configs(self): 363 def test_get_ref_configs(self):
363 self.mock_refs() 364 self.mock_refs()
364 365
365 self.mock(api, 'get_config_multi', mock.Mock()) 366 self.mock(api, 'get_config_multi', mock.Mock())
366 res = api.GetConfigMultiResponseMessage() 367 res = api.GetConfigMultiResponseMessage()
367 api.get_config_multi.return_value = res 368 api.get_config_multi.return_value = res
368 369
369 req = {'path': 'cq.cfg'} 370 req = {'path': 'cq.cfg'}
370 resp = self.call_api('get_ref_configs', req).json_body 371 resp = self.call_api('get_ref_configs', req).json_body
371 372
372 config_sets = api.get_config_multi.call_args[0][0] 373 config_sets = api.get_config_multi.call_args[0][0]
373 self.assertEqual( 374 self.assertEqual(
374 list(config_sets), 375 list(config_sets),
375 [ 376 [
376 'projects/chromium/refs/heads/master', 377 'projects/chromium/refs/heads/master',
377 'projects/chromium/refs/heads/release42', 378 'projects/chromium/refs/heads/release42',
378 'projects/v8/refs/heads/master', 379 'projects/v8/refs/heads/master',
379 'projects/v8/refs/heads/release42', 380 'projects/v8/refs/heads/release42',
380 ]) 381 ])
381 382
382 def test_get_ref_configs_without_permission(self): 383 def test_get_ref_configs_without_permission(self):
383 self.mock(api, 'get_projects', mock.Mock())
384 acl.has_project_access.return_value = False 384 acl.has_project_access.return_value = False
385 385
386 req = {'path': 'cq.cfg'} 386 req = {'path': 'cq.cfg'}
387 with self.call_should_fail(httplib.NOT_FOUND): 387 resp = self.call_api('get_ref_configs', req).json_body
388 self.call_api('get_ref_configs', req) 388 self.assertEqual(resp, {})
389 self.assertFalse(api.get_projects.called)
390 389
391 390
392 if __name__ == '__main__': 391 if __name__ == '__main__':
393 test_env.main() 392 test_env.main()
OLDNEW
« no previous file with comments | « appengine/config_service/api.py ('k') | appengine/config_service/common.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698