entitlements 0.1.7
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.
- checksums.yaml +7 -0
- data/VERSION +1 -0
- data/bin/deploy-entitlements +18 -0
- data/lib/entitlements/auditor/base.rb +163 -0
- data/lib/entitlements/backend/base_controller.rb +171 -0
- data/lib/entitlements/backend/base_provider.rb +55 -0
- data/lib/entitlements/backend/dummy/controller.rb +89 -0
- data/lib/entitlements/backend/dummy.rb +3 -0
- data/lib/entitlements/backend/ldap/controller.rb +188 -0
- data/lib/entitlements/backend/ldap/provider.rb +128 -0
- data/lib/entitlements/backend/ldap.rb +4 -0
- data/lib/entitlements/backend/member_of/controller.rb +203 -0
- data/lib/entitlements/backend/member_of.rb +3 -0
- data/lib/entitlements/cli.rb +121 -0
- data/lib/entitlements/data/groups/cached.rb +120 -0
- data/lib/entitlements/data/groups/calculated/base.rb +478 -0
- data/lib/entitlements/data/groups/calculated/filters/base.rb +93 -0
- data/lib/entitlements/data/groups/calculated/filters/member_of_group.rb +32 -0
- data/lib/entitlements/data/groups/calculated/modifiers/base.rb +38 -0
- data/lib/entitlements/data/groups/calculated/modifiers/expiration.rb +56 -0
- data/lib/entitlements/data/groups/calculated/ruby.rb +137 -0
- data/lib/entitlements/data/groups/calculated/rules/base.rb +35 -0
- data/lib/entitlements/data/groups/calculated/rules/group.rb +129 -0
- data/lib/entitlements/data/groups/calculated/rules/username.rb +41 -0
- data/lib/entitlements/data/groups/calculated/text.rb +337 -0
- data/lib/entitlements/data/groups/calculated/yaml.rb +171 -0
- data/lib/entitlements/data/groups/calculated.rb +290 -0
- data/lib/entitlements/data/groups.rb +13 -0
- data/lib/entitlements/data/people/combined.rb +197 -0
- data/lib/entitlements/data/people/dummy.rb +71 -0
- data/lib/entitlements/data/people/ldap.rb +142 -0
- data/lib/entitlements/data/people/yaml.rb +102 -0
- data/lib/entitlements/data/people.rb +58 -0
- data/lib/entitlements/extras/base.rb +40 -0
- data/lib/entitlements/extras/ldap_group/base.rb +20 -0
- data/lib/entitlements/extras/ldap_group/filters/member_of_ldap_group.rb +50 -0
- data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +69 -0
- data/lib/entitlements/extras/orgchart/base.rb +32 -0
- data/lib/entitlements/extras/orgchart/logic.rb +171 -0
- data/lib/entitlements/extras/orgchart/person_methods.rb +55 -0
- data/lib/entitlements/extras/orgchart/rules/direct_report.rb +62 -0
- data/lib/entitlements/extras/orgchart/rules/management.rb +59 -0
- data/lib/entitlements/extras.rb +82 -0
- data/lib/entitlements/models/action.rb +82 -0
- data/lib/entitlements/models/group.rb +280 -0
- data/lib/entitlements/models/person.rb +149 -0
- data/lib/entitlements/plugins/dummy.rb +22 -0
- data/lib/entitlements/plugins/group_of_names.rb +28 -0
- data/lib/entitlements/plugins/posix_group.rb +46 -0
- data/lib/entitlements/plugins.rb +13 -0
- data/lib/entitlements/rule/base.rb +74 -0
- data/lib/entitlements/service/ldap.rb +405 -0
- data/lib/entitlements/util/mirror.rb +42 -0
- data/lib/entitlements/util/override.rb +64 -0
- data/lib/entitlements/util/util.rb +219 -0
- data/lib/entitlements.rb +606 -0
- metadata +343 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Interact with rules that are stored in a simplified text file.
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
require_relative "../../../util/util"
|
6
|
+
|
7
|
+
module Entitlements
|
8
|
+
class Data
|
9
|
+
class Groups
|
10
|
+
class Calculated
|
11
|
+
class Text < Entitlements::Data::Groups::Calculated::Base
|
12
|
+
include ::Contracts::Core
|
13
|
+
C = ::Contracts
|
14
|
+
|
15
|
+
SEMICOLON_PREDICATES = %w[expiration]
|
16
|
+
|
17
|
+
# Standard interface: Calculate the members of this group.
|
18
|
+
#
|
19
|
+
# Takes no arguments.
|
20
|
+
#
|
21
|
+
# Returns a Set[String] with DN's of the people in the group.
|
22
|
+
Contract C::None => C::Or[:calculating, C::SetOf[Entitlements::Models::Person]]
|
23
|
+
def members
|
24
|
+
@members ||= begin
|
25
|
+
Entitlements.logger.debug "Calculating members from #{filename}"
|
26
|
+
members_from_rules(rules)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Standard interface: Get the description of this group.
|
31
|
+
#
|
32
|
+
# Takes no arguments.
|
33
|
+
#
|
34
|
+
# Returns a String with the group description, or "" if undefined.
|
35
|
+
Contract C::None => String
|
36
|
+
def description
|
37
|
+
return "" unless parsed_data.key?("description")
|
38
|
+
|
39
|
+
if parsed_data["description"]["!="].any?
|
40
|
+
fatal_message("The description cannot use '!=' operator in #{filename}!")
|
41
|
+
end
|
42
|
+
|
43
|
+
unless parsed_data["description"]["="].size == 1
|
44
|
+
fatal_message("The description key is duplicated in #{filename}!")
|
45
|
+
end
|
46
|
+
|
47
|
+
parsed_data["description"]["="].first.fetch(:key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Files can support modifiers that act independently of rules.
|
51
|
+
# This returns the modifiers from the file as a hash.
|
52
|
+
#
|
53
|
+
# Takes no arguments.
|
54
|
+
#
|
55
|
+
# Returns Hash[<String>key => <Object>value]
|
56
|
+
Contract C::None => C::HashOf[String => C::Any]
|
57
|
+
def modifiers
|
58
|
+
parse_with_prefix("modifier_")
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Get a hash of the filters defined in the group.
|
64
|
+
#
|
65
|
+
# Takes no arguments.
|
66
|
+
#
|
67
|
+
# Returns a Hash[String => :all/:none/List of strings].
|
68
|
+
Contract C::None => C::HashOf[String => C::Or[:all, :none, C::ArrayOf[String]]]
|
69
|
+
def initialize_filters
|
70
|
+
result = Entitlements::Data::Groups::Calculated.filters_default
|
71
|
+
|
72
|
+
parsed_data.each do |raw_key, val|
|
73
|
+
if raw_key == "filter_"
|
74
|
+
fatal_message("In #{filename}, cannot have a key named \"filter_\"!")
|
75
|
+
end
|
76
|
+
|
77
|
+
next unless raw_key.start_with?("filter_")
|
78
|
+
key = raw_key.sub(/\Afilter_/, "")
|
79
|
+
|
80
|
+
unless result.key?(key)
|
81
|
+
fatal_message("In #{filename}, the key #{raw_key} is invalid!")
|
82
|
+
end
|
83
|
+
|
84
|
+
if val["!="].any?
|
85
|
+
fatal_message("The filter #{key} cannot use '!=' operator in #{filename}!")
|
86
|
+
end
|
87
|
+
|
88
|
+
values = val["="].reject { |v| expired?(v[:expiration], filename) }.map { |v| v[:key].strip }
|
89
|
+
if values.size == 1 && (values.first == "all" || values.first == "none")
|
90
|
+
result[key] = values.first.to_sym
|
91
|
+
elsif values.size > 1 && (values.include?("all") || values.include?("none"))
|
92
|
+
fatal_message("In #{filename}, #{raw_key} cannot contain multiple entries when 'all' or 'none' is used!")
|
93
|
+
elsif values.size == 0
|
94
|
+
# This could happen if all of the specified filters were deleted due to expiration.
|
95
|
+
# In that case make no changes so the default gets used.
|
96
|
+
next
|
97
|
+
else
|
98
|
+
result[key] = values
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
result
|
103
|
+
end
|
104
|
+
|
105
|
+
# Files can support metadata intended for consumption by things other than LDAP.
|
106
|
+
# This returns the metadata from the file as a hash.
|
107
|
+
#
|
108
|
+
# Takes no arguments.
|
109
|
+
#
|
110
|
+
# Returns Hash[<String>key => <Object>value]
|
111
|
+
Contract C::None => C::HashOf[String => C::Any]
|
112
|
+
def initialize_metadata
|
113
|
+
parse_with_prefix("metadata_")
|
114
|
+
end
|
115
|
+
|
116
|
+
# Metadata and modifiers are parsed with nearly identical logic. In DRY spirit, use
|
117
|
+
# a single parsing method.
|
118
|
+
#
|
119
|
+
# prefix - String with the prefix expected for the key.
|
120
|
+
#
|
121
|
+
# Returns Hash[<String>key => <Object>value]
|
122
|
+
Contract String => C::HashOf[String => C::Any]
|
123
|
+
def parse_with_prefix(prefix)
|
124
|
+
result = {}
|
125
|
+
parsed_data.each do |raw_key, val|
|
126
|
+
if raw_key == "#{prefix}"
|
127
|
+
raise "In #{filename}, cannot have a key named \"#{prefix}\"!"
|
128
|
+
end
|
129
|
+
|
130
|
+
next unless raw_key.start_with?(prefix)
|
131
|
+
key = raw_key.sub(/\A#{prefix}/, "")
|
132
|
+
|
133
|
+
if val["!="].any?
|
134
|
+
fatal_message("The key #{raw_key} cannot use '!=' operator in #{filename}!")
|
135
|
+
end
|
136
|
+
|
137
|
+
unless val["="].size == 1
|
138
|
+
fatal_message("In #{filename}, the key #{raw_key} is repeated!")
|
139
|
+
end
|
140
|
+
|
141
|
+
unless val["="].first.keys == [:key]
|
142
|
+
settings = (val["="].first.keys - [:key]).map { |i| i.to_s.inspect }.join(",")
|
143
|
+
fatal_message("In #{filename}, the key #{raw_key} cannot have additional setting(s) #{settings}!")
|
144
|
+
end
|
145
|
+
|
146
|
+
result[key] = val["="].first.fetch(:key)
|
147
|
+
end
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
# Obtain the rule set from the content of the file and convert it to an object.
|
152
|
+
#
|
153
|
+
# Takes no arguments.
|
154
|
+
#
|
155
|
+
# Returns a Hash.
|
156
|
+
Contract C::None => C::HashOf[String => C::Any]
|
157
|
+
def rules
|
158
|
+
@rules ||= begin
|
159
|
+
ignored_keys = %w[description]
|
160
|
+
|
161
|
+
relevant_entries = parsed_data.reject { |k, _| ignored_keys.include?(k) }
|
162
|
+
relevant_entries.reject! { |k, _| k.start_with?("metadata_", "filter_", "modifier_") }
|
163
|
+
|
164
|
+
# Review all entries
|
165
|
+
affirmative = []
|
166
|
+
mandatory = []
|
167
|
+
negative = []
|
168
|
+
relevant_entries.each do |k, v|
|
169
|
+
function = function_for(k)
|
170
|
+
unless whitelisted_methods.member?(function)
|
171
|
+
Entitlements.logger.fatal "The method #{k.inspect} is not allowed in #{filename}!"
|
172
|
+
raise "The method #{k.inspect} is not allowed in #{filename}!"
|
173
|
+
end
|
174
|
+
|
175
|
+
add_relevant_entries!(affirmative, function, v["="], filename)
|
176
|
+
add_relevant_entries!(mandatory, function, v["&="], filename)
|
177
|
+
add_relevant_entries!(negative, function, v["!="], filename)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Expiration pre-processing: An entitlement that is expired as a whole should not
|
181
|
+
# raise an error about having no conditions.
|
182
|
+
if parsed_data.key?("modifier_expiration") && affirmative.empty?
|
183
|
+
exp_date = parsed_data.fetch("modifier_expiration").fetch("=").first.fetch(:key)
|
184
|
+
date = Entitlements::Util::Util.parse_date(exp_date)
|
185
|
+
return {"always" => false} if date <= Time.now.utc.to_date
|
186
|
+
end
|
187
|
+
|
188
|
+
# There has to be at least one affirmative condition, not just all negative ones.
|
189
|
+
# Override with `metadata_no_conditions_ok = true`.
|
190
|
+
if affirmative.empty?
|
191
|
+
return {"always" => false} if [true, "true"].include?(metadata["no_conditions_ok"])
|
192
|
+
fatal_message("No conditions were found in #{filename}!")
|
193
|
+
end
|
194
|
+
|
195
|
+
# Get base affirmative and negative rules.
|
196
|
+
result = affirmative_negative_rules(affirmative, negative)
|
197
|
+
|
198
|
+
# Apply any mandatory rules.
|
199
|
+
if mandatory.size == 1
|
200
|
+
old_result = result.dup
|
201
|
+
result = { "and" => [mandatory.first, old_result] }
|
202
|
+
elsif mandatory.size > 1
|
203
|
+
old_result = result.dup
|
204
|
+
result = { "and" => [{ "or" => mandatory }, old_result] }
|
205
|
+
end
|
206
|
+
|
207
|
+
# Return what we've got.
|
208
|
+
result
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Handle affirmative and negative rules.
|
213
|
+
#
|
214
|
+
# affirmative - An array of Hashes with rules.
|
215
|
+
# negative - An array of Hashes with rules.
|
216
|
+
#
|
217
|
+
# Returns appropriate and / or hash.
|
218
|
+
Contract C::ArrayOf[Hash], C::ArrayOf[Hash] => C::HashOf[String => C::Any]
|
219
|
+
def affirmative_negative_rules(affirmative, negative)
|
220
|
+
if negative.empty?
|
221
|
+
# This is a simplified file. Just OR all the conditions together. (For
|
222
|
+
# something more complicated, use YAML or ruby formats.)
|
223
|
+
{ "or" => affirmative }
|
224
|
+
else
|
225
|
+
# Each affirmative condition is OR'd, but any negative condition will veto.
|
226
|
+
# For something more complicated, use YAML or ruby formats.
|
227
|
+
{
|
228
|
+
"and" => [
|
229
|
+
{ "or" => affirmative },
|
230
|
+
{ "and" => negative.map { |condition| { "not" => condition } } }
|
231
|
+
]
|
232
|
+
}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Helper method to extract relevant entries from the parsed rules and concatenate them
|
237
|
+
# onto the given array.
|
238
|
+
#
|
239
|
+
# array_to_update - An Array which will have relevant rules concat'd to it.
|
240
|
+
# key - String with the key.
|
241
|
+
# rule_items - An Array of Hashes with the rules to evaluate.
|
242
|
+
# filename - Filename where rule is defined (used for error printing).
|
243
|
+
#
|
244
|
+
# Updates and returns array_to_update.
|
245
|
+
Contract C::ArrayOf[C::HashOf[String => String]], String, C::ArrayOf[C::HashOf[Symbol => String]], String => C::ArrayOf[C::HashOf[String => String]]
|
246
|
+
def add_relevant_entries!(array_to_update, key, rule_items, filename)
|
247
|
+
new_items = rule_items.reject { |item| expired?(item[:expiration], filename) }.map { |item| { key => item[:key] } }
|
248
|
+
array_to_update.concat new_items
|
249
|
+
end
|
250
|
+
|
251
|
+
# Return the parsed data from the file. This is called on demand and cached.
|
252
|
+
#
|
253
|
+
# Takes no arguments.
|
254
|
+
#
|
255
|
+
# Returns a Hash.
|
256
|
+
Contract C::None => C::HashOf[String => C::HashOf[String, C::ArrayOf[C::HashOf[Symbol, String]]]]
|
257
|
+
def parsed_data
|
258
|
+
@parsed_data ||= begin
|
259
|
+
result = {}
|
260
|
+
filter_keywords = Entitlements::Data::Groups::Calculated.filters_index.keys
|
261
|
+
content = File.read(filename).split(/\n/)
|
262
|
+
content.each do |raw_line|
|
263
|
+
line = raw_line.strip
|
264
|
+
|
265
|
+
# Ignore comments and blank lines
|
266
|
+
next if line.start_with?("#") || line == ""
|
267
|
+
|
268
|
+
# Ensure valid lines
|
269
|
+
unless line =~ /\A([\w\-]+)\s*([&!]?=)\s*(.+?)\s*\z/
|
270
|
+
Entitlements.logger.fatal "Unparseable line #{line.inspect} in #{filename}!"
|
271
|
+
raise "Unparseable line #{line.inspect} in #{filename}!"
|
272
|
+
end
|
273
|
+
|
274
|
+
# Parsing
|
275
|
+
raw_key, operator, val = Regexp.last_match(1), Regexp.last_match(2), Regexp.last_match(3)
|
276
|
+
|
277
|
+
key = if filter_keywords.include?(raw_key)
|
278
|
+
"filter_#{raw_key}"
|
279
|
+
elsif MODIFIERS.include?(raw_key)
|
280
|
+
"modifier_#{raw_key}"
|
281
|
+
else
|
282
|
+
raw_key
|
283
|
+
end
|
284
|
+
|
285
|
+
# Contractor function is used internally but may not be specified in the file by the user.
|
286
|
+
if key == "contractor"
|
287
|
+
Entitlements.logger.fatal "The method #{key.inspect} is not permitted in #{filename}!"
|
288
|
+
raise "Rule Error: #{key} is not a valid function in #{filename}!"
|
289
|
+
end
|
290
|
+
|
291
|
+
result[key] ||= {}
|
292
|
+
result[key]["="] ||= []
|
293
|
+
result[key]["!="] ||= []
|
294
|
+
result[key]["&="] ||= []
|
295
|
+
|
296
|
+
# Semicolon predicates
|
297
|
+
if key == "description"
|
298
|
+
result[key][operator] << { key: val }
|
299
|
+
else
|
300
|
+
result[key][operator] << parsed_predicate(val)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
result
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Parse predicate for a rule. Turn into a hash of { key: <String of Primary Value> + other keys in line }.
|
309
|
+
#
|
310
|
+
# val - The predicate string
|
311
|
+
#
|
312
|
+
# Returns a Hash.
|
313
|
+
Contract String => C::HashOf[Symbol, String]
|
314
|
+
def parsed_predicate(val)
|
315
|
+
v = val.sub(/\s*#.*\z/, "")
|
316
|
+
return { key: v } unless v.include?(";")
|
317
|
+
|
318
|
+
parts = v.split(/\s*;\s*/)
|
319
|
+
op_hash = { key: parts.shift }
|
320
|
+
parts.each do |part|
|
321
|
+
if part =~ /\A(\w+)\s*=\s*(\S+)\s*\z/
|
322
|
+
predicate_keyword, predicate_value = Regexp.last_match(1), Regexp.last_match(2)
|
323
|
+
unless SEMICOLON_PREDICATES.include?(predicate_keyword)
|
324
|
+
raise ArgumentError, "Rule Error: Invalid semicolon predicate #{predicate_keyword.inspect} in #{filename}!"
|
325
|
+
end
|
326
|
+
op_hash[predicate_keyword.to_sym] = predicate_value
|
327
|
+
else
|
328
|
+
raise ArgumentError, "Rule Error: Unparseable semicolon predicate #{part.inspect} in #{filename}!"
|
329
|
+
end
|
330
|
+
end
|
331
|
+
op_hash
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Interact with rules that are stored in a YAML file.
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module Entitlements
|
7
|
+
class Data
|
8
|
+
class Groups
|
9
|
+
class Calculated
|
10
|
+
class YAML < Entitlements::Data::Groups::Calculated::Base
|
11
|
+
include ::Contracts::Core
|
12
|
+
C = ::Contracts
|
13
|
+
|
14
|
+
# Standard interface: Calculate the members of this group.
|
15
|
+
#
|
16
|
+
# Takes no arguments.
|
17
|
+
#
|
18
|
+
# Returns a Set[String] with DN's of the people in the group.
|
19
|
+
Contract C::None => C::Or[:calculating, C::SetOf[Entitlements::Models::Person]]
|
20
|
+
def members
|
21
|
+
@members ||= begin
|
22
|
+
Entitlements.logger.debug "Calculating members from #{filename}"
|
23
|
+
members_from_rules(rules)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Standard interface: Get the description of this group.
|
28
|
+
#
|
29
|
+
# Takes no arguments.
|
30
|
+
#
|
31
|
+
# Returns a String with the group description, or "" if undefined.
|
32
|
+
Contract C::None => String
|
33
|
+
def description
|
34
|
+
parsed_data.fetch("description", "")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Files can support modifiers that act independently of rules.
|
38
|
+
# This returns the modifiers from the file as a hash.
|
39
|
+
#
|
40
|
+
# Takes no arguments.
|
41
|
+
#
|
42
|
+
# Returns Hash[<String>key => <Object>value]
|
43
|
+
Contract C::None => C::HashOf[String => C::Any]
|
44
|
+
def modifiers
|
45
|
+
parsed_data.select { |k, _v| MODIFIERS.include?(k) }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Get a hash of the filters defined in the group.
|
51
|
+
#
|
52
|
+
# Takes no arguments.
|
53
|
+
#
|
54
|
+
# Returns a Hash[String => :all/:none/List of strings].
|
55
|
+
Contract C::None => C::HashOf[String => C::Or[:all, :none, C::ArrayOf[String]]]
|
56
|
+
def initialize_filters
|
57
|
+
result = Entitlements::Data::Groups::Calculated.filters_default
|
58
|
+
return result unless parsed_data.key?("filters")
|
59
|
+
|
60
|
+
f = parsed_data["filters"]
|
61
|
+
unless f.is_a?(Hash)
|
62
|
+
raise ArgumentError, "For filters in #{filename}: expected Hash, got #{f.inspect}!"
|
63
|
+
end
|
64
|
+
|
65
|
+
f.each do |key, val|
|
66
|
+
unless result.key?(key)
|
67
|
+
raise ArgumentError, "Filter #{key} in #{filename} is invalid!"
|
68
|
+
end
|
69
|
+
|
70
|
+
values = if val.is_a?(String)
|
71
|
+
[val]
|
72
|
+
elsif val.is_a?(Array)
|
73
|
+
val
|
74
|
+
else
|
75
|
+
raise ArgumentError, "Value #{val.inspect} for #{key} in #{filename} is invalid!"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check for expiration
|
79
|
+
values.reject! { |v| v.is_a?(Hash) && expired?(v["expiration"].to_s, filename) }
|
80
|
+
values.map! { |v| v.is_a?(Hash) ? v.fetch("key") : v.strip }
|
81
|
+
|
82
|
+
if values.size == 1 && (values.first == "all" || values.first == "none")
|
83
|
+
result[key] = values.first.to_sym
|
84
|
+
elsif values.size > 1 && (values.include?("all") || values.include?("none"))
|
85
|
+
raise ArgumentError, "In #{filename}, #{key} cannot contain multiple entries when 'all' or 'none' is used!"
|
86
|
+
elsif values.size == 0
|
87
|
+
# This could happen if all of the specified filters were deleted due to expiration.
|
88
|
+
# In that case make no changes so the default gets used.
|
89
|
+
next
|
90
|
+
else
|
91
|
+
result[key] = values
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
# Files can support metadata intended for consumption by things other than LDAP.
|
99
|
+
# This returns the metadata from the file as a hash.
|
100
|
+
#
|
101
|
+
# Takes no arguments.
|
102
|
+
#
|
103
|
+
# Returns Hash[<String>key => <Object>value]
|
104
|
+
Contract C::None => C::HashOf[String => C::Any]
|
105
|
+
def initialize_metadata
|
106
|
+
return {} unless parsed_data.key?("metadata")
|
107
|
+
result = parsed_data["metadata"]
|
108
|
+
|
109
|
+
unless result.is_a?(Hash)
|
110
|
+
raise ArgumentError, "For metadata in #{filename}: expected Hash, got #{result.inspect}!"
|
111
|
+
end
|
112
|
+
|
113
|
+
result.each do |key, _|
|
114
|
+
next if key.is_a?(String)
|
115
|
+
raise ArgumentError, "For metadata in #{filename}: keys are expected to be strings, but #{key.inspect} is not!"
|
116
|
+
end
|
117
|
+
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
# Obtain the rule set from the YAML file and convert it to an object. Cache this the first
|
122
|
+
# time it happens, because this code is going to be called once per person!
|
123
|
+
#
|
124
|
+
# Takes no arguments.
|
125
|
+
#
|
126
|
+
# Returns a Hash.
|
127
|
+
Contract C::None => C::HashOf[String => C::Any]
|
128
|
+
def rules
|
129
|
+
@rules ||= begin
|
130
|
+
rules_hash = parsed_data["rules"]
|
131
|
+
unless rules_hash.is_a?(Hash)
|
132
|
+
raise "Expected to find 'rules' as a Hash in #{filename}, but got #{rules_hash.class}!"
|
133
|
+
end
|
134
|
+
remove_expired_rules(rules_hash)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Remove expired rules from the rules hash.
|
139
|
+
#
|
140
|
+
# rules_hash - Hash of rules.
|
141
|
+
#
|
142
|
+
# Returns the updated hash that has no expired rules in it.
|
143
|
+
Contract C::HashOf[String => C::Any] => C::HashOf[String => C::Any]
|
144
|
+
def remove_expired_rules(rules_hash)
|
145
|
+
if rules_hash.keys.size == 1
|
146
|
+
if rules_hash.values.first.is_a?(Array)
|
147
|
+
return { rules_hash.keys.first => rules_hash.values.first.map { |v| remove_expired_rules(v) }.reject { |h| h.empty? } }
|
148
|
+
else
|
149
|
+
return rules_hash
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
expdate = rules_hash.delete("expiration")
|
154
|
+
return {} if expired?(expdate, filename)
|
155
|
+
rules_hash
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return the parsed data from the file. This is called on demand and cached.
|
159
|
+
#
|
160
|
+
# Takes no arguments.
|
161
|
+
#
|
162
|
+
# Returns a Hash.
|
163
|
+
Contract C::None => C::HashOf[String => C::Any]
|
164
|
+
def parsed_data
|
165
|
+
@parsed_data ||= ::YAML.load(File.read(filename))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|