chef-zero 15.0.17 → 15.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +36 -31
  3. data/LICENSE +201 -201
  4. data/Rakefile +73 -68
  5. data/bin/chef-zero +111 -111
  6. data/chef-zero.gemspec +34 -33
  7. data/lib/chef_zero/chef_data/acl_path.rb +140 -140
  8. data/lib/chef_zero/chef_data/cookbook_data.rb +237 -237
  9. data/lib/chef_zero/chef_data/data_normalizer.rb +276 -276
  10. data/lib/chef_zero/chef_data/default_creator.rb +476 -476
  11. data/lib/chef_zero/data_store/data_already_exists_error.rb +29 -29
  12. data/lib/chef_zero/data_store/data_error.rb +32 -32
  13. data/lib/chef_zero/data_store/data_not_found_error.rb +29 -29
  14. data/lib/chef_zero/data_store/default_facade.rb +143 -147
  15. data/lib/chef_zero/data_store/interface_v1.rb +67 -67
  16. data/lib/chef_zero/data_store/interface_v2.rb +18 -18
  17. data/lib/chef_zero/data_store/memory_store.rb +33 -33
  18. data/lib/chef_zero/data_store/memory_store_v2.rb +159 -159
  19. data/lib/chef_zero/data_store/raw_file_store.rb +143 -143
  20. data/lib/chef_zero/data_store/v1_to_v2_adapter.rb +150 -150
  21. data/lib/chef_zero/data_store/v2_to_v1_adapter.rb +105 -105
  22. data/lib/chef_zero/dist.rb +9 -9
  23. data/lib/chef_zero/endpoints/acl_endpoint.rb +39 -39
  24. data/lib/chef_zero/endpoints/acls_endpoint.rb +41 -41
  25. data/lib/chef_zero/endpoints/actor_default_key_endpoint.rb +78 -78
  26. data/lib/chef_zero/endpoints/actor_endpoint.rb +184 -184
  27. data/lib/chef_zero/endpoints/actor_key_endpoint.rb +62 -62
  28. data/lib/chef_zero/endpoints/actor_keys_endpoint.rb +129 -129
  29. data/lib/chef_zero/endpoints/actors_endpoint.rb +104 -104
  30. data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +32 -32
  31. data/lib/chef_zero/endpoints/container_endpoint.rb +22 -22
  32. data/lib/chef_zero/endpoints/containers_endpoint.rb +25 -25
  33. data/lib/chef_zero/endpoints/controls_endpoint.rb +16 -16
  34. data/lib/chef_zero/endpoints/cookbook_artifact_endpoint.rb +24 -24
  35. data/lib/chef_zero/endpoints/cookbook_artifact_identifier_endpoint.rb +68 -68
  36. data/lib/chef_zero/endpoints/cookbook_artifacts_endpoint.rb +34 -34
  37. data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -39
  38. data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +136 -136
  39. data/lib/chef_zero/endpoints/cookbooks_base.rb +80 -80
  40. data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +19 -19
  41. data/lib/chef_zero/endpoints/data_bag_endpoint.rb +45 -45
  42. data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -25
  43. data/lib/chef_zero/endpoints/data_bags_endpoint.rb +23 -23
  44. data/lib/chef_zero/endpoints/dummy_endpoint.rb +29 -29
  45. data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -24
  46. data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +126 -126
  47. data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -22
  48. data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -33
  49. data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -23
  50. data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -22
  51. data/lib/chef_zero/endpoints/environment_role_endpoint.rb +36 -36
  52. data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -22
  53. data/lib/chef_zero/endpoints/group_endpoint.rb +20 -20
  54. data/lib/chef_zero/endpoints/groups_endpoint.rb +13 -13
  55. data/lib/chef_zero/endpoints/license_endpoint.rb +25 -25
  56. data/lib/chef_zero/endpoints/node_endpoint.rb +34 -34
  57. data/lib/chef_zero/endpoints/node_identifiers_endpoint.rb +22 -22
  58. data/lib/chef_zero/endpoints/nodes_endpoint.rb +34 -34
  59. data/lib/chef_zero/endpoints/not_found_endpoint.rb +11 -11
  60. data/lib/chef_zero/endpoints/organization_association_request_endpoint.rb +22 -22
  61. data/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb +30 -30
  62. data/lib/chef_zero/endpoints/organization_authenticate_user_endpoint.rb +26 -26
  63. data/lib/chef_zero/endpoints/organization_endpoint.rb +47 -47
  64. data/lib/chef_zero/endpoints/organization_user_base.rb +15 -15
  65. data/lib/chef_zero/endpoints/organization_user_default_key_endpoint.rb +16 -16
  66. data/lib/chef_zero/endpoints/organization_user_endpoint.rb +26 -26
  67. data/lib/chef_zero/endpoints/organization_user_key_endpoint.rb +17 -17
  68. data/lib/chef_zero/endpoints/organization_user_keys_endpoint.rb +17 -17
  69. data/lib/chef_zero/endpoints/organization_users_endpoint.rb +43 -43
  70. data/lib/chef_zero/endpoints/organization_validator_key_endpoint.rb +20 -20
  71. data/lib/chef_zero/endpoints/organizations_endpoint.rb +61 -61
  72. data/lib/chef_zero/endpoints/policies_endpoint.rb +26 -26
  73. data/lib/chef_zero/endpoints/policy_endpoint.rb +24 -24
  74. data/lib/chef_zero/endpoints/policy_group_endpoint.rb +46 -46
  75. data/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb +83 -83
  76. data/lib/chef_zero/endpoints/policy_groups_endpoint.rb +38 -38
  77. data/lib/chef_zero/endpoints/policy_revision_endpoint.rb +66 -66
  78. data/lib/chef_zero/endpoints/policy_revisions_endpoint.rb +15 -15
  79. data/lib/chef_zero/endpoints/principal_endpoint.rb +55 -55
  80. data/lib/chef_zero/endpoints/rest_list_endpoint.rb +42 -42
  81. data/lib/chef_zero/endpoints/rest_object_endpoint.rb +78 -78
  82. data/lib/chef_zero/endpoints/role_endpoint.rb +16 -16
  83. data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -14
  84. data/lib/chef_zero/endpoints/sandbox_endpoint.rb +27 -27
  85. data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +51 -51
  86. data/lib/chef_zero/endpoints/search_endpoint.rb +208 -208
  87. data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -18
  88. data/lib/chef_zero/endpoints/server_api_version_endpoint.rb +14 -14
  89. data/lib/chef_zero/endpoints/system_recovery_endpoint.rb +30 -30
  90. data/lib/chef_zero/endpoints/universe_endpoint.rb +15 -15
  91. data/lib/chef_zero/endpoints/user_association_request_endpoint.rb +41 -41
  92. data/lib/chef_zero/endpoints/user_association_requests_count_endpoint.rb +19 -19
  93. data/lib/chef_zero/endpoints/user_association_requests_endpoint.rb +19 -19
  94. data/lib/chef_zero/endpoints/user_organizations_endpoint.rb +22 -22
  95. data/lib/chef_zero/endpoints/version_endpoint.rb +13 -13
  96. data/lib/chef_zero/log.rb +7 -7
  97. data/lib/chef_zero/rest_base.rb +332 -332
  98. data/lib/chef_zero/rest_error_response.rb +11 -11
  99. data/lib/chef_zero/rest_request.rb +84 -88
  100. data/lib/chef_zero/rest_router.rb +72 -72
  101. data/lib/chef_zero/rspec.rb +355 -355
  102. data/lib/chef_zero/server.rb +730 -730
  103. data/lib/chef_zero/socketless_server_map.rb +92 -93
  104. data/lib/chef_zero/solr/query/binary_operator.rb +52 -52
  105. data/lib/chef_zero/solr/query/phrase.rb +23 -23
  106. data/lib/chef_zero/solr/query/range_query.rb +46 -46
  107. data/lib/chef_zero/solr/query/regexpable_query.rb +30 -30
  108. data/lib/chef_zero/solr/query/subquery.rb +37 -37
  109. data/lib/chef_zero/solr/query/term.rb +45 -45
  110. data/lib/chef_zero/solr/query/unary_operator.rb +41 -41
  111. data/lib/chef_zero/solr/solr_doc.rb +53 -53
  112. data/lib/chef_zero/solr/solr_parser.rb +208 -208
  113. data/lib/chef_zero/version.rb +3 -3
  114. data/lib/chef_zero.rb +10 -10
  115. data/spec/run_oc_pedant.rb +226 -226
  116. data/spec/search_spec.rb +36 -36
  117. data/spec/server_spec.rb +96 -96
  118. data/spec/socketless_server_map_spec.rb +74 -74
  119. data/spec/support/oc_pedant.rb +149 -149
  120. data/spec/support/secrets.json +6 -6
  121. data/spec/support/stickywicket.pem +27 -27
  122. metadata +35 -18
@@ -1,476 +1,476 @@
1
- require_relative "acl_path"
2
-
3
- module ChefZero
4
- module ChefData
5
- #
6
- # The DefaultCreator creates default values when you ask for them.
7
- # - It relies on created and deleted being called when things get
8
- # created and deleted, so that it knows the owners of said objects
9
- # and knows to eliminate default values on delete.
10
- # - get, list and exists? get data.
11
- #
12
- class DefaultCreator
13
- def initialize(data, single_org, osc_compat, superusers = nil)
14
- @data = data
15
- @single_org = single_org
16
- @osc_compat = osc_compat
17
- @superusers = superusers || DEFAULT_SUPERUSERS
18
- clear
19
- end
20
-
21
- attr_reader :data
22
- attr_reader :single_org
23
- attr_reader :osc_compat
24
- attr_reader :creators
25
- attr_writer :deleted
26
-
27
- PERMISSIONS = %w{create read update delete grant}.freeze
28
- DEFAULT_SUPERUSERS = %w{pivotal}.freeze
29
-
30
- def clear
31
- @creators = { [] => @superusers }
32
- @deleted = {}
33
- end
34
-
35
- def deleted(path)
36
- # acl deletes mean nothing, they are entirely subservient to their
37
- # parent object
38
- if path[0] == "acls" || (path[0] == "organizations" && path[2] == "acls")
39
- return false
40
- end
41
-
42
- result = exists?(path)
43
- @deleted[path] = true
44
- result
45
- end
46
-
47
- def deleted?(path)
48
- 1.upto(path.size) do |index|
49
- return true if @deleted[path[0..-index]]
50
- end
51
- false
52
- end
53
-
54
- def created(path, creator, create_parents)
55
- # If a parent has been deleted, we will need to clear that.
56
- deleted_index = nil
57
- 0.upto(path.size - 1) do |index|
58
- deleted_index = index if @deleted[path[0..index]]
59
- end
60
-
61
- # Walk up the tree, setting the creator on anything that doesn't exist
62
- # (anything that is either deleted or was never created)
63
- while (deleted_index && path.size > deleted_index) || !@creators[path]
64
- @creators[path] = creator ? [ creator ] : []
65
- @deleted.delete(path)
66
- # Only do this once if create_parents is false
67
- break if !create_parents || path.size == 0
68
-
69
- path = path[0..-2]
70
- end
71
- end
72
-
73
- def superusers
74
- @creators[[]]
75
- end
76
-
77
- def get(path)
78
- return nil if deleted?(path)
79
-
80
- result = case path[0]
81
- when "acls"
82
- # /acls/*
83
- object_path = AclPath.get_object_path(path)
84
- if data_exists?(object_path)
85
- default_acl(path)
86
- end
87
-
88
- when "containers"
89
- if path.size == 2 && exists?(path)
90
- {}
91
- end
92
-
93
- when "users"
94
- if path.size == 2 && data.exists?(path)
95
- # User is empty user
96
- {}
97
- end
98
-
99
- when "organizations"
100
- if path.size >= 2
101
- # /organizations/*/**
102
- if data.exists_dir?(path[0..1])
103
- get_org_default(path)
104
- end
105
- end
106
- end
107
-
108
- result
109
- end
110
-
111
- def list(path)
112
- return nil if deleted?(path)
113
-
114
- if path.size == 0
115
- return %w{containers users organizations acls}
116
- end
117
-
118
- case path[0]
119
- when "acls"
120
- if path.size == 1
121
- [ "root" ] + (data.list(path + [ "containers" ]) - [ "organizations" ])
122
- else
123
- data.list(AclPath.get_object_path(path))
124
- end
125
-
126
- when "containers"
127
- %w{containers users organizations}
128
-
129
- when "users"
130
- superusers
131
-
132
- when "organizations"
133
- if path.size == 1
134
- single_org ? [ single_org ] : []
135
- elsif path.size >= 2 && data.exists_dir?(path[0..1])
136
- list_org_default(path)
137
- end
138
- end
139
- end
140
-
141
- def exists?(path)
142
- return true if path.size == 0
143
-
144
- parent_list = list(path[0..-2])
145
- parent_list && parent_list.include?(path[-1])
146
- end
147
-
148
- protected
149
-
150
- DEFAULT_ORG_SPINE = {
151
- "clients" => {},
152
- "cookbook_artifacts" => {},
153
- "cookbooks" => {},
154
- "data" => {},
155
- "environments" => %w{_default},
156
- "file_store" => {
157
- "checksums" => {},
158
- },
159
- "nodes" => {},
160
- "policies" => {},
161
- "policy_groups" => {},
162
- "roles" => {},
163
- "sandboxes" => {},
164
- "users" => {},
165
-
166
- "org" => {},
167
- "containers" => %w{clients containers cookbook_artifacts cookbooks data environments groups nodes policies policy_groups roles sandboxes},
168
- "groups" => %w{admins billing-admins clients users},
169
- "association_requests" => {},
170
- }.freeze
171
-
172
- def list_org_default(path)
173
- if path.size >= 3 && path[2] == "acls"
174
- if path.size == 3
175
- # /organizations/ORG/acls
176
- return [ "root" ] + data.list(path[0..1] + [ "containers" ])
177
- elsif path.size == 4
178
- # /organizations/ORG/acls/TYPE
179
- return data.list(path[0..1] + [ path[3] ])
180
- else
181
- return nil
182
- end
183
- end
184
-
185
- value = DEFAULT_ORG_SPINE
186
- 2.upto(path.size - 1) do |index|
187
- value = nil if @deleted[path[0..index]]
188
- break unless value
189
-
190
- value = value[path[index]]
191
- end
192
-
193
- result = if value.is_a?(Hash)
194
- value.keys
195
- elsif value
196
- value
197
- end
198
-
199
- if path.size == 3
200
- if path[2] == "clients"
201
- result << "#{path[1]}-validator"
202
- if osc_compat
203
- result << "#{path[1]}-webui"
204
- end
205
- elsif path[2] == "users"
206
- if osc_compat
207
- result << "admin"
208
- end
209
- end
210
- end
211
-
212
- result
213
- end
214
-
215
- def get_org_default(path)
216
- if path[2] == "acls"
217
- get_org_acl_default(path)
218
-
219
- elsif path.size >= 4
220
- if path[2] == "containers" && path.size == 4
221
- if exists?(path)
222
- return {}
223
- else
224
- return nil
225
- end
226
- end
227
-
228
- # /organizations/(*)/clients/\1-validator
229
- # /organizations/*/environments/_default
230
- # /organizations/*/groups/{admins,billing-admins,clients,users}
231
- case path[2..-1].join("/")
232
- when "clients/#{path[1]}-validator"
233
- { "validator" => "true" }
234
-
235
- when "clients/#{path[1]}-webui", "users/admin"
236
- if osc_compat
237
- { "admin" => "true" }
238
- end
239
-
240
- when "environments/_default"
241
- { "description" => "The default Chef environment" }
242
-
243
- when "groups/admins"
244
- admins = data.list(path[0..1] + [ "users" ]).select do |name|
245
- user = FFI_Yajl::Parser.parse(data.get(path[0..1] + [ "users", name ]))
246
- user["admin"]
247
- end
248
- admins += data.list(path[0..1] + [ "clients" ]).select do |name|
249
- client = FFI_Yajl::Parser.parse(data.get(path[0..1] + [ "clients", name ]))
250
- client["admin"]
251
- end
252
- admins += @creators[path[0..1]] if @creators[path[0..1]]
253
- { "actors" => admins.uniq }
254
-
255
- when "groups/billing-admins"
256
- {}
257
-
258
- when "groups/clients"
259
- { "clients" => data.list(path[0..1] + [ "clients" ]) }
260
-
261
- when "groups/users"
262
- users = data.list(path[0..1] + [ "users" ])
263
- users |= @creators[path[0..1]] if @creators[path[0..1]]
264
- { "users" => users }
265
-
266
- when "org"
267
- {}
268
-
269
- end
270
- end
271
- end
272
-
273
- def get_org_acl_default(path)
274
- object_path = AclPath.get_object_path(path)
275
- # The actual things containers correspond to don't have to exist, as
276
- # long as the container does
277
- return nil unless data_exists?(object_path)
278
-
279
- basic_acl =
280
- case path[3..-1].join("/")
281
- when "root", "containers/containers", "containers/groups"
282
- {
283
- "create" => { "groups" => %w{admins} },
284
- "read" => { "groups" => %w{admins users} },
285
- "update" => { "groups" => %w{admins} },
286
- "delete" => { "groups" => %w{admins} },
287
- "grant" => { "groups" => %w{admins} },
288
- }
289
- when "containers/environments", "containers/roles",
290
- "containers/policy_groups", "containers/policies",
291
- "containers/cookbooks", "containers/cookbook_artifacts",
292
- "containers/data"
293
- {
294
- "create" => { "groups" => %w{admins users} },
295
- "read" => { "groups" => %w{admins users clients} },
296
- "update" => { "groups" => %w{admins users} },
297
- "delete" => { "groups" => %w{admins users} },
298
- "grant" => { "groups" => %w{admins} },
299
- }
300
- when "containers/nodes"
301
- {
302
- "create" => { "groups" => %w{admins users clients} },
303
- "read" => { "groups" => %w{admins users clients} },
304
- "update" => { "groups" => %w{admins users} },
305
- "delete" => { "groups" => %w{admins users} },
306
- "grant" => { "groups" => %w{admins} },
307
- }
308
- when "containers/clients"
309
- {
310
- "create" => { "groups" => %w{admins} },
311
- "read" => { "groups" => %w{admins users} },
312
- "update" => { "groups" => %w{admins} },
313
- "delete" => { "groups" => %w{admins users} },
314
- "grant" => { "groups" => %w{admins} },
315
- }
316
- when "containers/sandboxes"
317
- {
318
- "create" => { "groups" => %w{admins users} },
319
- "read" => { "groups" => %w{admins} },
320
- "update" => { "groups" => %w{admins} },
321
- "delete" => { "groups" => %w{admins} },
322
- "grant" => { "groups" => %w{admins} },
323
- }
324
- when "groups/admins", "groups/clients", "groups/users"
325
- {
326
- "create" => { "groups" => %w{admins} },
327
- "read" => { "groups" => %w{admins} },
328
- "update" => { "groups" => %w{admins} },
329
- "delete" => { "groups" => %w{admins} },
330
- "grant" => { "groups" => %w{admins} },
331
- }
332
- when "groups/billing-admins"
333
- {
334
- "create" => { "groups" => %w{} },
335
- "read" => { "groups" => %w{billing-admins} },
336
- "update" => { "groups" => %w{billing-admins} },
337
- "delete" => { "groups" => %w{} },
338
- "grant" => { "groups" => %w{} },
339
- }
340
- else
341
- {}
342
- end
343
-
344
- default_acl(path, basic_acl)
345
- end
346
-
347
- def get_owners(acl_path)
348
- unknown_owners = []
349
- path = AclPath.get_object_path(acl_path)
350
- if path
351
-
352
- # Non-validator clients own themselves.
353
- if path.size == 4 && path[0] == "organizations" && path[2] == "clients"
354
- begin
355
- client = FFI_Yajl::Parser.parse(data.get(path))
356
- unless client["validator"]
357
- unknown_owners |= [ path[3] ]
358
- end
359
- rescue
360
- unknown_owners |= [ path[3] ]
361
- end
362
-
363
- # Add creators as owners (except any validator clients).
364
- if @creators[path]
365
- @creators[path].each do |creator|
366
- begin
367
- client = FFI_Yajl::Parser.parse(data.get(path[0..2] + [ creator ]))
368
- next if client["validator"]
369
- rescue
370
- end
371
- unknown_owners |= [ creator ]
372
- end
373
- end
374
- else
375
- unknown_owners |= @creators[path] if @creators[path]
376
- end
377
- owners = filter_owners(path, unknown_owners)
378
-
379
- # ANGRY
380
- # Non-default containers do not get superusers added to them,
381
- # because reasons.
382
- unless path.size == 4 && path[0] == "organizations" && path[2] == "containers" && !exists?(path)
383
- owners[:users] += superusers
384
- end
385
- else
386
- owners = { clients: [], users: [] }
387
- end
388
-
389
- owners[:users].uniq!
390
- owners[:clients].uniq!
391
- owners
392
- end
393
-
394
- # Figures out if an object was created by a user or client.
395
- # If the object does not exist in the context
396
- # of an organization, it can only be a user
397
- #
398
- # This isn't perfect, because we are never explicitly told
399
- # if a requestor creating an object is a user or client -
400
- # but it gets us reasonably close
401
- def filter_owners(path, unknown_owners)
402
- owners = { clients: [], users: [] }
403
- unknown_owners.each do |entity|
404
- if path[0] == "organizations" && path.length > 2
405
- begin
406
- data.get(["organizations", path[1], "clients", entity])
407
- owners[:clients] |= [ entity ]
408
- rescue
409
- owners[:users] |= [ entity ]
410
- end
411
- else
412
- owners[:users] |= [ entity ]
413
- end
414
- end
415
- owners
416
- end
417
-
418
- def default_acl(acl_path, acl = {})
419
- owners = get_owners(acl_path)
420
- container_acl = nil
421
- PERMISSIONS.each do |perm|
422
- acl[perm] ||= {}
423
- acl[perm]["users"] = owners[:users]
424
- acl[perm]["clients"] = owners[:clients]
425
- acl[perm]["groups"] ||= begin
426
- # When we create containers, we don't merge groups (not sure why).
427
- if acl_path[0] == "organizations" && acl_path[3] == "containers"
428
- []
429
- else
430
- container_acl ||= get_container_acl(acl_path) || {}
431
- (container_acl[perm] ? container_acl[perm]["groups"] : []) || []
432
- end
433
- end
434
- acl[perm]["actors"] = acl[perm]["clients"] + acl[perm]["users"]
435
- end
436
- acl
437
- end
438
-
439
- def get_container_acl(acl_path)
440
- parent_path = AclPath.parent_acl_data_path(acl_path)
441
- if parent_path
442
- FFI_Yajl::Parser.parse(data.get(parent_path))
443
- else
444
- nil
445
- end
446
- end
447
-
448
- def data_exists?(path)
449
- if is_dir?(path)
450
- data.exists_dir?(path)
451
- else
452
- data.exists?(path)
453
- end
454
- end
455
-
456
- def is_dir?(path)
457
- case path.size
458
- when 0, 1
459
- true
460
- when 2
461
- path[0] == "organizations" || (path[0] == "acls" && path[1] != "root")
462
- when 3
463
- # If it has a container, it is a directory.
464
- path[0] == "organizations" &&
465
- (path[2] == "acls" || data.exists?(path[0..1] + [ "containers", path[2] ]))
466
- when 4
467
- path[0] == "organizations" && (
468
- (path[2] == "acls" && path[1] != "root") ||
469
- %w{cookbooks cookbook_artifacts data policies policy_groups}.include?(path[2]))
470
- else
471
- false
472
- end
473
- end
474
- end
475
- end
476
- end
1
+ require_relative "acl_path"
2
+
3
+ module ChefZero
4
+ module ChefData
5
+ #
6
+ # The DefaultCreator creates default values when you ask for them.
7
+ # - It relies on created and deleted being called when things get
8
+ # created and deleted, so that it knows the owners of said objects
9
+ # and knows to eliminate default values on delete.
10
+ # - get, list and exists? get data.
11
+ #
12
+ class DefaultCreator
13
+ def initialize(data, single_org, osc_compat, superusers = nil)
14
+ @data = data
15
+ @single_org = single_org
16
+ @osc_compat = osc_compat
17
+ @superusers = superusers || DEFAULT_SUPERUSERS
18
+ clear
19
+ end
20
+
21
+ attr_reader :data
22
+ attr_reader :single_org
23
+ attr_reader :osc_compat
24
+ attr_reader :creators
25
+ attr_writer :deleted
26
+
27
+ PERMISSIONS = %w{create read update delete grant}.freeze
28
+ DEFAULT_SUPERUSERS = %w{pivotal}.freeze
29
+
30
+ def clear
31
+ @creators = { [] => @superusers }
32
+ @deleted = {}
33
+ end
34
+
35
+ def deleted(path)
36
+ # acl deletes mean nothing, they are entirely subservient to their
37
+ # parent object
38
+ if path[0] == "acls" || (path[0] == "organizations" && path[2] == "acls")
39
+ return false
40
+ end
41
+
42
+ result = exists?(path)
43
+ @deleted[path] = true
44
+ result
45
+ end
46
+
47
+ def deleted?(path)
48
+ 1.upto(path.size) do |index|
49
+ return true if @deleted[path[0..-index]]
50
+ end
51
+ false
52
+ end
53
+
54
+ def created(path, creator, create_parents)
55
+ # If a parent has been deleted, we will need to clear that.
56
+ deleted_index = nil
57
+ 0.upto(path.size - 1) do |index|
58
+ deleted_index = index if @deleted[path[0..index]]
59
+ end
60
+
61
+ # Walk up the tree, setting the creator on anything that doesn't exist
62
+ # (anything that is either deleted or was never created)
63
+ while (deleted_index && path.size > deleted_index) || !@creators[path]
64
+ @creators[path] = creator ? [ creator ] : []
65
+ @deleted.delete(path)
66
+ # Only do this once if create_parents is false
67
+ break if !create_parents || path.size == 0
68
+
69
+ path = path[0..-2]
70
+ end
71
+ end
72
+
73
+ def superusers
74
+ @creators[[]]
75
+ end
76
+
77
+ def get(path)
78
+ return nil if deleted?(path)
79
+
80
+ result = case path[0]
81
+ when "acls"
82
+ # /acls/*
83
+ object_path = AclPath.get_object_path(path)
84
+ if data_exists?(object_path)
85
+ default_acl(path)
86
+ end
87
+
88
+ when "containers"
89
+ if path.size == 2 && exists?(path)
90
+ {}
91
+ end
92
+
93
+ when "users"
94
+ if path.size == 2 && data.exists?(path)
95
+ # User is empty user
96
+ {}
97
+ end
98
+
99
+ when "organizations"
100
+ if path.size >= 2
101
+ # /organizations/*/**
102
+ if data.exists_dir?(path[0..1])
103
+ get_org_default(path)
104
+ end
105
+ end
106
+ end
107
+
108
+ result
109
+ end
110
+
111
+ def list(path)
112
+ return nil if deleted?(path)
113
+
114
+ if path.size == 0
115
+ return %w{containers users organizations acls}
116
+ end
117
+
118
+ case path[0]
119
+ when "acls"
120
+ if path.size == 1
121
+ [ "root" ] + (data.list(path + [ "containers" ]) - [ "organizations" ])
122
+ else
123
+ data.list(AclPath.get_object_path(path))
124
+ end
125
+
126
+ when "containers"
127
+ %w{containers users organizations}
128
+
129
+ when "users"
130
+ superusers
131
+
132
+ when "organizations"
133
+ if path.size == 1
134
+ single_org ? [ single_org ] : []
135
+ elsif path.size >= 2 && data.exists_dir?(path[0..1])
136
+ list_org_default(path)
137
+ end
138
+ end
139
+ end
140
+
141
+ def exists?(path)
142
+ return true if path.size == 0
143
+
144
+ parent_list = list(path[0..-2])
145
+ parent_list && parent_list.include?(path[-1])
146
+ end
147
+
148
+ protected
149
+
150
+ DEFAULT_ORG_SPINE = {
151
+ "clients" => {},
152
+ "cookbook_artifacts" => {},
153
+ "cookbooks" => {},
154
+ "data" => {},
155
+ "environments" => %w{_default},
156
+ "file_store" => {
157
+ "checksums" => {},
158
+ },
159
+ "nodes" => {},
160
+ "policies" => {},
161
+ "policy_groups" => {},
162
+ "roles" => {},
163
+ "sandboxes" => {},
164
+ "users" => {},
165
+
166
+ "org" => {},
167
+ "containers" => %w{clients containers cookbook_artifacts cookbooks data environments groups nodes policies policy_groups roles sandboxes},
168
+ "groups" => %w{admins billing-admins clients users},
169
+ "association_requests" => {},
170
+ }.freeze
171
+
172
+ def list_org_default(path)
173
+ if path.size >= 3 && path[2] == "acls"
174
+ if path.size == 3
175
+ # /organizations/ORG/acls
176
+ return [ "root" ] + data.list(path[0..1] + [ "containers" ])
177
+ elsif path.size == 4
178
+ # /organizations/ORG/acls/TYPE
179
+ return data.list(path[0..1] + [ path[3] ])
180
+ else
181
+ return nil
182
+ end
183
+ end
184
+
185
+ value = DEFAULT_ORG_SPINE
186
+ 2.upto(path.size - 1) do |index|
187
+ value = nil if @deleted[path[0..index]]
188
+ break unless value
189
+
190
+ value = value[path[index]]
191
+ end
192
+
193
+ result = if value.is_a?(Hash)
194
+ value.keys
195
+ elsif value
196
+ value
197
+ end
198
+
199
+ if path.size == 3
200
+ if path[2] == "clients"
201
+ result << "#{path[1]}-validator"
202
+ if osc_compat
203
+ result << "#{path[1]}-webui"
204
+ end
205
+ elsif path[2] == "users"
206
+ if osc_compat
207
+ result << "admin"
208
+ end
209
+ end
210
+ end
211
+
212
+ result
213
+ end
214
+
215
+ def get_org_default(path)
216
+ if path[2] == "acls"
217
+ get_org_acl_default(path)
218
+
219
+ elsif path.size >= 4
220
+ if path[2] == "containers" && path.size == 4
221
+ if exists?(path)
222
+ return {}
223
+ else
224
+ return nil
225
+ end
226
+ end
227
+
228
+ # /organizations/(*)/clients/\1-validator
229
+ # /organizations/*/environments/_default
230
+ # /organizations/*/groups/{admins,billing-admins,clients,users}
231
+ case path[2..-1].join("/")
232
+ when "clients/#{path[1]}-validator"
233
+ { "validator" => "true" }
234
+
235
+ when "clients/#{path[1]}-webui", "users/admin"
236
+ if osc_compat
237
+ { "admin" => "true" }
238
+ end
239
+
240
+ when "environments/_default"
241
+ { "description" => "The default Chef environment" }
242
+
243
+ when "groups/admins"
244
+ admins = data.list(path[0..1] + [ "users" ]).select do |name|
245
+ user = FFI_Yajl::Parser.parse(data.get(path[0..1] + [ "users", name ]))
246
+ user["admin"]
247
+ end
248
+ admins += data.list(path[0..1] + [ "clients" ]).select do |name|
249
+ client = FFI_Yajl::Parser.parse(data.get(path[0..1] + [ "clients", name ]))
250
+ client["admin"]
251
+ end
252
+ admins += @creators[path[0..1]] if @creators[path[0..1]]
253
+ { "actors" => admins.uniq }
254
+
255
+ when "groups/billing-admins"
256
+ {}
257
+
258
+ when "groups/clients"
259
+ { "clients" => data.list(path[0..1] + [ "clients" ]) }
260
+
261
+ when "groups/users"
262
+ users = data.list(path[0..1] + [ "users" ])
263
+ users |= @creators[path[0..1]] if @creators[path[0..1]]
264
+ { "users" => users }
265
+
266
+ when "org"
267
+ {}
268
+
269
+ end
270
+ end
271
+ end
272
+
273
+ def get_org_acl_default(path)
274
+ object_path = AclPath.get_object_path(path)
275
+ # The actual things containers correspond to don't have to exist, as
276
+ # long as the container does
277
+ return nil unless data_exists?(object_path)
278
+
279
+ basic_acl =
280
+ case path[3..-1].join("/")
281
+ when "root", "containers/containers", "containers/groups"
282
+ {
283
+ "create" => { "groups" => %w{admins} },
284
+ "read" => { "groups" => %w{admins users} },
285
+ "update" => { "groups" => %w{admins} },
286
+ "delete" => { "groups" => %w{admins} },
287
+ "grant" => { "groups" => %w{admins} },
288
+ }
289
+ when "containers/environments", "containers/roles",
290
+ "containers/policy_groups", "containers/policies",
291
+ "containers/cookbooks", "containers/cookbook_artifacts",
292
+ "containers/data"
293
+ {
294
+ "create" => { "groups" => %w{admins users} },
295
+ "read" => { "groups" => %w{admins users clients} },
296
+ "update" => { "groups" => %w{admins users} },
297
+ "delete" => { "groups" => %w{admins users} },
298
+ "grant" => { "groups" => %w{admins} },
299
+ }
300
+ when "containers/nodes"
301
+ {
302
+ "create" => { "groups" => %w{admins users clients} },
303
+ "read" => { "groups" => %w{admins users clients} },
304
+ "update" => { "groups" => %w{admins users} },
305
+ "delete" => { "groups" => %w{admins users} },
306
+ "grant" => { "groups" => %w{admins} },
307
+ }
308
+ when "containers/clients"
309
+ {
310
+ "create" => { "groups" => %w{admins} },
311
+ "read" => { "groups" => %w{admins users} },
312
+ "update" => { "groups" => %w{admins} },
313
+ "delete" => { "groups" => %w{admins users} },
314
+ "grant" => { "groups" => %w{admins} },
315
+ }
316
+ when "containers/sandboxes"
317
+ {
318
+ "create" => { "groups" => %w{admins users} },
319
+ "read" => { "groups" => %w{admins} },
320
+ "update" => { "groups" => %w{admins} },
321
+ "delete" => { "groups" => %w{admins} },
322
+ "grant" => { "groups" => %w{admins} },
323
+ }
324
+ when "groups/admins", "groups/clients", "groups/users"
325
+ {
326
+ "create" => { "groups" => %w{admins} },
327
+ "read" => { "groups" => %w{admins} },
328
+ "update" => { "groups" => %w{admins} },
329
+ "delete" => { "groups" => %w{admins} },
330
+ "grant" => { "groups" => %w{admins} },
331
+ }
332
+ when "groups/billing-admins"
333
+ {
334
+ "create" => { "groups" => %w{} },
335
+ "read" => { "groups" => %w{billing-admins} },
336
+ "update" => { "groups" => %w{billing-admins} },
337
+ "delete" => { "groups" => %w{} },
338
+ "grant" => { "groups" => %w{} },
339
+ }
340
+ else
341
+ {}
342
+ end
343
+
344
+ default_acl(path, basic_acl)
345
+ end
346
+
347
+ def get_owners(acl_path)
348
+ unknown_owners = []
349
+ path = AclPath.get_object_path(acl_path)
350
+ if path
351
+
352
+ # Non-validator clients own themselves.
353
+ if path.size == 4 && path[0] == "organizations" && path[2] == "clients"
354
+ begin
355
+ client = FFI_Yajl::Parser.parse(data.get(path))
356
+ unless client["validator"]
357
+ unknown_owners |= [ path[3] ]
358
+ end
359
+ rescue
360
+ unknown_owners |= [ path[3] ]
361
+ end
362
+
363
+ # Add creators as owners (except any validator clients).
364
+ if @creators[path]
365
+ @creators[path].each do |creator|
366
+ begin
367
+ client = FFI_Yajl::Parser.parse(data.get(path[0..2] + [ creator ]))
368
+ next if client["validator"]
369
+ rescue
370
+ end
371
+ unknown_owners |= [ creator ]
372
+ end
373
+ end
374
+ else
375
+ unknown_owners |= @creators[path] if @creators[path]
376
+ end
377
+ owners = filter_owners(path, unknown_owners)
378
+
379
+ # ANGRY
380
+ # Non-default containers do not get superusers added to them,
381
+ # because reasons.
382
+ unless path.size == 4 && path[0] == "organizations" && path[2] == "containers" && !exists?(path)
383
+ owners[:users] += superusers
384
+ end
385
+ else
386
+ owners = { clients: [], users: [] }
387
+ end
388
+
389
+ owners[:users].uniq!
390
+ owners[:clients].uniq!
391
+ owners
392
+ end
393
+
394
+ # Figures out if an object was created by a user or client.
395
+ # If the object does not exist in the context
396
+ # of an organization, it can only be a user
397
+ #
398
+ # This isn't perfect, because we are never explicitly told
399
+ # if a requestor creating an object is a user or client -
400
+ # but it gets us reasonably close
401
+ def filter_owners(path, unknown_owners)
402
+ owners = { clients: [], users: [] }
403
+ unknown_owners.each do |entity|
404
+ if path[0] == "organizations" && path.length > 2
405
+ begin
406
+ data.get(["organizations", path[1], "clients", entity])
407
+ owners[:clients] |= [ entity ]
408
+ rescue
409
+ owners[:users] |= [ entity ]
410
+ end
411
+ else
412
+ owners[:users] |= [ entity ]
413
+ end
414
+ end
415
+ owners
416
+ end
417
+
418
+ def default_acl(acl_path, acl = {})
419
+ owners = get_owners(acl_path)
420
+ container_acl = nil
421
+ PERMISSIONS.each do |perm|
422
+ acl[perm] ||= {}
423
+ acl[perm]["users"] = owners[:users]
424
+ acl[perm]["clients"] = owners[:clients]
425
+
426
+ # When we create containers, we don't merge groups (not sure why).
427
+ acl[perm]["groups"] ||= if acl_path[0] == "organizations" && acl_path[3] == "containers"
428
+ []
429
+ else
430
+ container_acl ||= get_container_acl(acl_path) || {}
431
+ (container_acl[perm] ? container_acl[perm]["groups"] : []) || []
432
+ end
433
+
434
+ acl[perm]["actors"] = acl[perm]["clients"] + acl[perm]["users"]
435
+ end
436
+ acl
437
+ end
438
+
439
+ def get_container_acl(acl_path)
440
+ parent_path = AclPath.parent_acl_data_path(acl_path)
441
+ if parent_path
442
+ FFI_Yajl::Parser.parse(data.get(parent_path))
443
+ else
444
+ nil
445
+ end
446
+ end
447
+
448
+ def data_exists?(path)
449
+ if is_dir?(path)
450
+ data.exists_dir?(path)
451
+ else
452
+ data.exists?(path)
453
+ end
454
+ end
455
+
456
+ def is_dir?(path)
457
+ case path.size
458
+ when 0, 1
459
+ true
460
+ when 2
461
+ path[0] == "organizations" || (path[0] == "acls" && path[1] != "root")
462
+ when 3
463
+ # If it has a container, it is a directory.
464
+ path[0] == "organizations" &&
465
+ (path[2] == "acls" || data.exists?(path[0..1] + [ "containers", path[2] ]))
466
+ when 4
467
+ path[0] == "organizations" && (
468
+ (path[2] == "acls" && path[1] != "root") ||
469
+ %w{cookbooks cookbook_artifacts data policies policy_groups}.include?(path[2]))
470
+ else
471
+ false
472
+ end
473
+ end
474
+ end
475
+ end
476
+ end