conjur-policy-parser 0.12.0

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.
@@ -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