conjur-policy-parser 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,417 @@
1
+ module Conjur
2
+ module Policy
3
+ module Types
4
+ # An inheritable class attribute which is cloned by subclasses so the attribute
5
+ # can be a mutable thing such as a Hash.
6
+ #
7
+ # https://raw.githubusercontent.com/apotonick/uber/master/lib/uber/inheritable_attr.rb
8
+ module InheritableAttribute
9
+ def inheritable_attr(name, options={})
10
+ instance_eval %Q{
11
+ def #{name}=(v)
12
+ @#{name} = v
13
+ end
14
+
15
+ def #{name}
16
+ return @#{name} if instance_variable_defined?(:@#{name})
17
+ @#{name} = InheritableAttribute.inherit_for(self, :#{name}, #{options})
18
+ end
19
+ }
20
+ end
21
+
22
+ def self.inherit_for(klass, name, options={})
23
+ return unless klass.superclass.respond_to?(name)
24
+
25
+ value = klass.superclass.send(name) # could be nil
26
+
27
+ return value if options[:clone] == false
28
+ Clone.(value) # this could be dynamic, allowing other inheritance strategies.
29
+ end
30
+
31
+ class Clone
32
+ # The second argument allows injecting more types.
33
+ def self.call(value, uncloneable=uncloneable())
34
+ uncloneable.each { |klass| return value if value.kind_of?(klass) }
35
+ value.clone
36
+ end
37
+
38
+ def self.uncloneable
39
+ [Symbol, TrueClass, FalseClass, NilClass]
40
+ end
41
+ end
42
+ end
43
+
44
+ # Methods which type-check and transform attributes. Type-checking can be done by
45
+ # duck-typing, with +is_a?+, or by a procedure.
46
+ module TypeChecking
47
+ # This is the primary function of the module.
48
+ #
49
+ # +value+ an input value
50
+ # +type_name+ used only for error messages.
51
+ # +test_function+ a class or function which will determine if the value is already the correct type.
52
+ # +converter+ if the +test_function+ fails, the converter is called to coerce the type.
53
+ # It should return +nil+ if its unable to do so.
54
+ def expect_type attr_name, value, type_name, test_function, converter = nil
55
+ if test_function.is_a?(Class)
56
+ cls = test_function
57
+ test_function = lambda{ value.is_a?(cls) }
58
+ end
59
+ if test_function.call
60
+ value
61
+ elsif converter && ( v = converter.call )
62
+ v
63
+ else
64
+ name = value.class.respond_to?(:short_name) ? value.class.short_name : value.class.name
65
+ raise "Expected a #{type_name} for field '#{attr_name}', got #{name}"
66
+ end
67
+ end
68
+
69
+ # Duck-type roles.
70
+ def test_role r
71
+ r.respond_to?(:role?) && r.role?
72
+ end
73
+
74
+ # Duck-type resources.
75
+ def test_resource r
76
+ r.respond_to?(:resource?) && r.resource?
77
+ end
78
+
79
+ # If it's a Record
80
+ def expect_record name, value
81
+ expect_type name, value, "Record", lambda{ value.is_a?(Record) }
82
+ end
83
+
84
+ # If it's a Layer
85
+ def expect_layer name, value
86
+ expect_type name, value, "Layer", lambda{ value.is_a?(Layer) }
87
+ end
88
+
89
+ # If it looks like a resource.
90
+ def expect_resource name, value
91
+ expect_type name, value, "Resource", lambda{ test_resource value }
92
+ end
93
+
94
+ # If it looks like a role.
95
+ def expect_role name, value
96
+ expect_type name, value, "Role", lambda{ test_role value }
97
+ end
98
+
99
+ # +value+ may be a Member; Roles can also be converted to Members.
100
+ def expect_member name, value
101
+ expect_type name,
102
+ value,
103
+ "Member",
104
+ Member,
105
+ lambda{ Member.new(value) if test_role(value) }
106
+ end
107
+
108
+ # +value+ must be a Permission.
109
+ def expect_permission name, value
110
+ expect_type name,
111
+ value,
112
+ "Permission",
113
+ Permission
114
+ end
115
+
116
+ # +value+ must be a String.
117
+ def expect_string name, value
118
+ expect_type name,
119
+ value,
120
+ "string",
121
+ String
122
+ end
123
+
124
+ # +value+ must be a Integer.
125
+ def expect_integer name, value
126
+ expect_type name,
127
+ value,
128
+ "integer",
129
+ Integer
130
+ end
131
+
132
+ # +value+ can be a Hash, or an object which implements +to_h+.
133
+ def expect_hash name, value
134
+ expect_type name,
135
+ value,
136
+ "hash",
137
+ lambda{ value.is_a?(Hash)},
138
+ lambda{ value.to_h.stringify_keys if value.respond_to?(:to_h) }
139
+ end
140
+
141
+ # +v+ must be +true+ or +false+.
142
+ def expect_boolean name, v
143
+ v = true if v == "true"
144
+ v = false if v == "false"
145
+ expect_type name,
146
+ v,
147
+ "boolean",
148
+ lambda{ [ true, false ].member?(v) }
149
+ end
150
+
151
+ # +values+ can be an instance of +type+ (as determined by the type-checking methods), or
152
+ # it must be an array of them.
153
+ def expect_array name, kind, values
154
+ # Hash gets converted to an array of key/value pairs by Array
155
+ is_hash = values.kind_of?(Hash)
156
+ values = [values] if is_hash
157
+
158
+ result = Array(values).map do |v|
159
+ send "expect_#{kind}", name, v
160
+ end
161
+
162
+ (values.is_a?(Array) and not is_hash) ? result : result[0]
163
+ end
164
+ end
165
+
166
+ # Define type-checked attributes, using the facilities defined in
167
+ # +TypeChecking+.
168
+ module AttributeDefinition
169
+ # Define a singular field.
170
+ #
171
+ # +attr+ the name of the field
172
+ # +kind+ the type of the field, which corresponds to a +TypeChecking+ method.
173
+ # +type+ a DSL object type which the parser should use to process the field.
174
+ # This option is not used for simple kinds like :boolean and :string, because they are
175
+ # not structured objects.
176
+ def define_field attr, kind, type = nil, dsl_accessor = false
177
+ register_yaml_field attr.to_s, type if type
178
+ register_field attr.to_s, kind if kind
179
+
180
+ if dsl_accessor
181
+ define_method attr do |*args|
182
+ v = args.shift
183
+ if v
184
+ existing = self.instance_variable_get("@#{attr}")
185
+ value = if existing
186
+ Array(existing) + [ v ]
187
+ else
188
+ v
189
+ end
190
+ self.instance_variable_set("@#{attr}", self.class.expect_array(attr, kind, value))
191
+ else
192
+ self.instance_variable_get("@#{attr}")
193
+ end
194
+ end
195
+ else
196
+ define_method attr do
197
+ self.instance_variable_get("@#{attr}")
198
+ end
199
+ end
200
+ define_method "#{attr}=" do |v|
201
+ self.instance_variable_set("@#{attr}", self.class.expect_array(attr, kind, v))
202
+ end
203
+ end
204
+
205
+ # Define a plural field. A plural field is basically just an alias to the singular field.
206
+ # For example, a plural field called +members+ is really just an alias to +member+. Both
207
+ # +member+ and +members+ will accept single values or Arrays of values.
208
+ def define_plural_field attr, kind, type = nil, dsl_accessor = false
209
+ define_field attr, kind.to_s, type, dsl_accessor
210
+
211
+ register_yaml_field attr.to_s.pluralize, type if type
212
+
213
+ define_method attr.to_s.pluralize do |*args|
214
+ send attr, *args
215
+ end
216
+ define_method "#{attr.to_s.pluralize}=" do |v|
217
+ send "#{attr}=", v
218
+ end
219
+ end
220
+
221
+ # This is the primary method used by concrete types to define their attributes.
222
+ #
223
+ # +attr+ the singularized attribute name.
224
+ #
225
+ # Options:
226
+ # +type+ a structured type to be constructed by the parser. If not provided, the type
227
+ # may be inferred from the attribute name (e.g. an attribute called :member is the type +Member+).
228
+ # +kind+ the symbolic name of the type. Inferred from the type, if the type is provided. Otherwise
229
+ # it's mandatory.
230
+ # +singular+ by default, attributes accept multiple values. This flag restricts the attribute
231
+ # to a single value only.
232
+ def attribute attr, options = {}
233
+ type = options[:type]
234
+ begin
235
+ type ||= Conjur::Policy::Types.const_get(attr.to_s.capitalize)
236
+ rescue NameError
237
+ end
238
+ type = nil if type == String
239
+ kind = options[:kind]
240
+ kind ||= type.short_name.downcase.to_sym if type
241
+
242
+ raise "Attribute :kind must be defined, explicitly or inferred from :type" unless kind
243
+
244
+ if options[:singular]
245
+ define_field attr, kind, type, options[:dsl_accessor]
246
+ else
247
+ define_plural_field attr, kind, type, options[:dsl_accessor]
248
+ end
249
+ end
250
+
251
+ # Ruby type for attribute name.
252
+ def yaml_field_type name
253
+ self.yaml_fields[name]
254
+ end
255
+
256
+ # Is there a Ruby type for a named field?
257
+ def yaml_field? name
258
+ !!self.yaml_fields[name]
259
+ end
260
+
261
+ # Is there a semantic kind for a named field?
262
+ def field? name
263
+ !!self.fields[name]
264
+ end
265
+
266
+ protected
267
+
268
+ # +nodoc+
269
+ def register_yaml_field field_name, type
270
+ raise "YAML field #{field_name} already defined on #{self.name} as #{self.yaml_fields[field_name]}" if self.yaml_field?(field_name)
271
+ self.yaml_fields[field_name] = type
272
+ end
273
+
274
+ # +nodoc+
275
+ def register_field field_name, kind
276
+ raise "YAML field #{field_name} already defined on #{self.name} as #{self.fields[field_name]}" if self.field?(field_name)
277
+ self.fields[field_name] = kind
278
+ end
279
+ end
280
+
281
+ # Base class for implementing structured DSL object types such as Role, User, etc.
282
+ #
283
+ # To define a type:
284
+ #
285
+ # * Inherit from this class
286
+ # * Define attributes using +attribute+
287
+ #
288
+ # Your new type will automatically be registered with the YAML parser with a tag
289
+ # corresponding to the lower-cased short name of the class.
290
+ class Base
291
+ extend InheritableAttribute
292
+ extend TypeChecking
293
+ extend AttributeDefinition
294
+
295
+ # Stores a Markdown description of the type.
296
+ inheritable_attr :description
297
+
298
+ # Stores a YAML example for the type.
299
+ inheritable_attr :example
300
+
301
+ # Stores the mapping from attribute names to Ruby class names that will be constructed
302
+ # to populate the attribute.
303
+ inheritable_attr :yaml_fields
304
+
305
+ # Stores the mapping from attribute names to semantic kind names.
306
+ inheritable_attr :fields
307
+
308
+ # +nodoc+
309
+ self.yaml_fields = {}
310
+
311
+ # +nodoc+
312
+ self.fields = {}
313
+
314
+ # Things aren't roles by default
315
+ def role?
316
+ false
317
+ end
318
+
319
+ def id_attribute; 'id'; end
320
+
321
+ def custom_attribute_names
322
+ [ ]
323
+ end
324
+
325
+ def resource?
326
+ false
327
+ end
328
+
329
+ def role?
330
+ false
331
+ end
332
+
333
+ # Gets all 'child' records.
334
+ def referenced_records
335
+ result = []
336
+ instance_variables.map do |var|
337
+ value = instance_variable_get var
338
+ Array(value).each do |val|
339
+ result.push val if val.is_a?(Conjur::Policy::Types::Base)
340
+ end
341
+ end
342
+ result.flatten
343
+ end
344
+
345
+ class << self
346
+ # Hook to register the YAML type.
347
+ def inherited cls
348
+ cls.register_yaml_type cls.short_name.underscore.gsub('_', '-')
349
+ end
350
+
351
+ # The last token in the ::-separated class name.
352
+ def short_name
353
+ self.name.demodulize
354
+ end
355
+
356
+ alias simple_name short_name
357
+
358
+ def register_yaml_type simple_name
359
+ ::YAML.add_tag "!#{simple_name}", self
360
+ end
361
+ end
362
+ end
363
+
364
+ # Define DSL accessor for Role +member+ field.
365
+ module RoleMemberDSL
366
+ def self.included(base)
367
+ base.module_eval do
368
+ alias member_accessor member
369
+
370
+ def member r = nil, admin_option = false
371
+ if r
372
+ member = Member.new(r)
373
+ member.admin = true if admin_option == true
374
+ if self.member
375
+ self.member = Array(self.member).push(member)
376
+ else
377
+ self.member = member
378
+ end
379
+ else
380
+ member_accessor
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ # Define DSL accessor for Resource +role+ field.
388
+ module ResourceMemberDSL
389
+ def self.included(base)
390
+ base.module_eval do
391
+ alias role_accessor role
392
+
393
+ def role r = nil, grant_option = nil
394
+ if r
395
+ role = Member.new(r)
396
+ role.admin = true if grant_option == true
397
+ if self.role
398
+ self.role = Array(self.role) + [ role ]
399
+ else
400
+ self.role = role
401
+ end
402
+ else
403
+ role_accessor
404
+ end
405
+ end
406
+ end
407
+ end
408
+ end
409
+
410
+ module AutomaticRoleDSL
411
+ def automatic_role record, role_name
412
+ AutomaticRole.new(record, role_name)
413
+ end
414
+ end
415
+ end
416
+ end
417
+ end
@@ -0,0 +1,40 @@
1
+ module Conjur::Policy::Types
2
+ class Create < Base
3
+ attribute :record
4
+
5
+ self.description = %(
6
+ Create a record of any type.
7
+
8
+ A record can be a [Role](#reference/role) or a [Resource](#reference/resource).
9
+
10
+ Creating records can be done explicitly using this node type, or
11
+ implicitly. Examples of both are given immediately below.
12
+
13
+ When a record is created explicitly, it's an error if the record already exists.
14
+ When a record is created implicitly, the record will be found-or-created, and its
15
+ state (owner, fields and annotations) will be updated to match the policy declaration.
16
+ )
17
+
18
+ self.example = %(
19
+ - !user research # implicit record creation
20
+ - !create # explicit record creation
21
+ record: !user research
22
+ - !create
23
+ record: !group experiment
24
+ - !create
25
+ record: !role control
26
+ kind: experimental_control
27
+ owner: !user research
28
+ )
29
+
30
+ def to_s
31
+ messages = [ "Create #{record}" ]
32
+ if record.resource?
33
+ (record.annotations||{}).each do |k,v|
34
+ messages.push " Set annotation '#{k}'"
35
+ end
36
+ end
37
+ messages.join("\n")
38
+ end
39
+ end
40
+ end