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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/VERSION +1 -0
  3. data/bin/deploy-entitlements +18 -0
  4. data/lib/entitlements/auditor/base.rb +163 -0
  5. data/lib/entitlements/backend/base_controller.rb +171 -0
  6. data/lib/entitlements/backend/base_provider.rb +55 -0
  7. data/lib/entitlements/backend/dummy/controller.rb +89 -0
  8. data/lib/entitlements/backend/dummy.rb +3 -0
  9. data/lib/entitlements/backend/ldap/controller.rb +188 -0
  10. data/lib/entitlements/backend/ldap/provider.rb +128 -0
  11. data/lib/entitlements/backend/ldap.rb +4 -0
  12. data/lib/entitlements/backend/member_of/controller.rb +203 -0
  13. data/lib/entitlements/backend/member_of.rb +3 -0
  14. data/lib/entitlements/cli.rb +121 -0
  15. data/lib/entitlements/data/groups/cached.rb +120 -0
  16. data/lib/entitlements/data/groups/calculated/base.rb +478 -0
  17. data/lib/entitlements/data/groups/calculated/filters/base.rb +93 -0
  18. data/lib/entitlements/data/groups/calculated/filters/member_of_group.rb +32 -0
  19. data/lib/entitlements/data/groups/calculated/modifiers/base.rb +38 -0
  20. data/lib/entitlements/data/groups/calculated/modifiers/expiration.rb +56 -0
  21. data/lib/entitlements/data/groups/calculated/ruby.rb +137 -0
  22. data/lib/entitlements/data/groups/calculated/rules/base.rb +35 -0
  23. data/lib/entitlements/data/groups/calculated/rules/group.rb +129 -0
  24. data/lib/entitlements/data/groups/calculated/rules/username.rb +41 -0
  25. data/lib/entitlements/data/groups/calculated/text.rb +337 -0
  26. data/lib/entitlements/data/groups/calculated/yaml.rb +171 -0
  27. data/lib/entitlements/data/groups/calculated.rb +290 -0
  28. data/lib/entitlements/data/groups.rb +13 -0
  29. data/lib/entitlements/data/people/combined.rb +197 -0
  30. data/lib/entitlements/data/people/dummy.rb +71 -0
  31. data/lib/entitlements/data/people/ldap.rb +142 -0
  32. data/lib/entitlements/data/people/yaml.rb +102 -0
  33. data/lib/entitlements/data/people.rb +58 -0
  34. data/lib/entitlements/extras/base.rb +40 -0
  35. data/lib/entitlements/extras/ldap_group/base.rb +20 -0
  36. data/lib/entitlements/extras/ldap_group/filters/member_of_ldap_group.rb +50 -0
  37. data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +69 -0
  38. data/lib/entitlements/extras/orgchart/base.rb +32 -0
  39. data/lib/entitlements/extras/orgchart/logic.rb +171 -0
  40. data/lib/entitlements/extras/orgchart/person_methods.rb +55 -0
  41. data/lib/entitlements/extras/orgchart/rules/direct_report.rb +62 -0
  42. data/lib/entitlements/extras/orgchart/rules/management.rb +59 -0
  43. data/lib/entitlements/extras.rb +82 -0
  44. data/lib/entitlements/models/action.rb +82 -0
  45. data/lib/entitlements/models/group.rb +280 -0
  46. data/lib/entitlements/models/person.rb +149 -0
  47. data/lib/entitlements/plugins/dummy.rb +22 -0
  48. data/lib/entitlements/plugins/group_of_names.rb +28 -0
  49. data/lib/entitlements/plugins/posix_group.rb +46 -0
  50. data/lib/entitlements/plugins.rb +13 -0
  51. data/lib/entitlements/rule/base.rb +74 -0
  52. data/lib/entitlements/service/ldap.rb +405 -0
  53. data/lib/entitlements/util/mirror.rb +42 -0
  54. data/lib/entitlements/util/override.rb +64 -0
  55. data/lib/entitlements/util/util.rb +219 -0
  56. data/lib/entitlements.rb +606 -0
  57. metadata +343 -0
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "calculated/base"
4
+ require_relative "calculated/ruby"
5
+ require_relative "calculated/text"
6
+ require_relative "calculated/yaml"
7
+
8
+ # Calculate groups that should exist and the contents of each based on a set of rules
9
+ # defined within a directory. The calculation of members is global across the entire
10
+ # entitlements system, so this is a singleton class.
11
+
12
+ module Entitlements
13
+ class Data
14
+ class Groups
15
+ class Calculated
16
+ include ::Contracts::Core
17
+ C = ::Contracts
18
+
19
+ FILE_EXTENSIONS = {
20
+ "rb" => "Entitlements::Data::Groups::Calculated::Ruby",
21
+ "txt" => "Entitlements::Data::Groups::Calculated::Text",
22
+ "yaml" => "Entitlements::Data::Groups::Calculated::YAML"
23
+ }
24
+
25
+ @groups_in_ou_cache = {}
26
+ @groups_cache = {}
27
+ @config_cache = {}
28
+
29
+ # Reset all module state
30
+ #
31
+ # Takes no arguments
32
+ def self.reset!
33
+ @rules_index = {
34
+ "group" => Entitlements::Data::Groups::Calculated::Rules::Group,
35
+ "username" => Entitlements::Data::Groups::Calculated::Rules::Username
36
+ }
37
+
38
+ @filters_index = {}
39
+ @groups_in_ou_cache = {}
40
+ @groups_cache = {}
41
+ @config_cache = {}
42
+ end
43
+
44
+ # Construct a group object.
45
+ #
46
+ # Takes no arguments.
47
+ #
48
+ # Returns a Entitlements::Models::Group object.
49
+ Contract String => Entitlements::Models::Group
50
+ def self.read(dn)
51
+ return @groups_cache[dn] if @groups_cache[dn]
52
+ raise "read(#{dn.inspect}) does not support calculation at this time. Please use read_all() first to build cache."
53
+ end
54
+
55
+ # Calculate all groups within the specified OU and enumerate their members.
56
+ # Results are cached for future runs so the read() method is faster.
57
+ #
58
+ # ou_key - String with the key from the configuration file.
59
+ # cfg_obj - Hash with the configuration for that key from the configuration file.
60
+ #
61
+ # Returns a Set of Strings (DNs) of the groups in this OU.
62
+ Contract String, C::HashOf[String => C::Any], C::KeywordArgs[
63
+ skip_broken_references: C::Optional[C::Bool]
64
+ ] => C::SetOf[String]
65
+ def self.read_all(ou_key, cfg_obj, skip_broken_references: false)
66
+ return read_mirror(ou_key, cfg_obj) if cfg_obj["mirror"]
67
+
68
+ @config_cache[ou_key] ||= cfg_obj
69
+ @groups_in_ou_cache[ou_key] ||= begin
70
+ Entitlements.logger.debug "Calculating all groups for #{ou_key}"
71
+ Entitlements.logger.debug "!!! skip_broken_references is enabled" if skip_broken_references
72
+
73
+ result = Set.new
74
+ Entitlements.cache[:file_objects] ||= {}
75
+
76
+ # Iterate over all the files in the configuration directory for this OU
77
+ path = Entitlements::Util::Util.path_for_group(ou_key)
78
+ Dir.glob(File.join(path, "*")).each do |filename|
79
+ # If it's a directory, skip it for now.
80
+ if File.directory?(filename)
81
+ next
82
+ end
83
+
84
+ # If the file is ignored (e.g. documentation) then skip it.
85
+ if Entitlements::IGNORED_FILES.member?(File.basename(filename))
86
+ next
87
+ end
88
+
89
+ # Determine the group DN. The CN will be the filname without its extension.
90
+ file_without_extension = File.basename(filename).sub(/\.\w+\z/, "")
91
+ unless file_without_extension =~ /\A[\w\-]+\z/
92
+ raise "Illegal LDAP group name #{file_without_extension.inspect} in #{ou_key}!"
93
+ end
94
+ group_dn = ["cn=#{file_without_extension}", cfg_obj.fetch("base")].join(",")
95
+
96
+ # Use the ruleset to build the group.
97
+ options = { skip_broken_references: skip_broken_references }
98
+
99
+ Entitlements.cache[:file_objects][filename] ||= ruleset(filename: filename, config: cfg_obj, options: options)
100
+ @groups_cache[group_dn] = Entitlements::Models::Group.new(
101
+ dn: group_dn,
102
+ members: Entitlements.cache[:file_objects][filename].modified_filtered_members,
103
+ description: Entitlements.cache[:file_objects][filename].description,
104
+ metadata: Entitlements.cache[:file_objects][filename].metadata.merge("_filename" => filename)
105
+ )
106
+ result.add group_dn
107
+ end
108
+
109
+ result
110
+ end
111
+ end
112
+
113
+ # Return the group cache as a hash.
114
+ #
115
+ # Takes no arguments.
116
+ #
117
+ # Returns a hash { dn => Entitlements::Models::Group }
118
+ # :nocov:
119
+ Contract C::None => C::HashOf[String => Entitlements::Models::Group]
120
+ def self.to_h
121
+ @groups_cache
122
+ end
123
+ # :nocov:
124
+
125
+ # Get the entire output organized by OU.
126
+ #
127
+ # Takes no arguments.
128
+ #
129
+ # Returns a Hash of OU to the configuration and group objects it contains.
130
+ Contract C::None => C::HashOf[String => { config: C::HashOf[String => C::Any], groups: C::HashOf[String => Entitlements::Models::Group]}]
131
+ def self.all_groups
132
+ @groups_in_ou_cache.map do |ou_key, dn_in_ou|
133
+ if @config_cache.key?(ou_key)
134
+ [
135
+ ou_key,
136
+ {
137
+ config: @config_cache[ou_key],
138
+ groups: dn_in_ou.sort.map { |dn| [dn, @groups_cache.fetch(dn)] }.to_h
139
+ }
140
+ ]
141
+ else
142
+ nil
143
+ end
144
+ end.compact.to_h
145
+ end
146
+
147
+ # Calculate the groups within the specified mirrored OU. This requires that
148
+ # read_all() has run already on the mirrored OU. This will not re-calculate
149
+ # the results, but rather just duplicate the results and adjust the OUs.
150
+ #
151
+ # ou_key - String with the key from the configuration file.
152
+ # cfg_obj - Hash with the configuration for that key from the configuration file.
153
+ #
154
+ # Returns a Set of Strings (DNs) of the groups in this OU.
155
+ Contract String, C::HashOf[String => C::Any] => C::SetOf[String]
156
+ def self.read_mirror(ou_key, cfg_obj)
157
+ @groups_in_ou_cache[ou_key] ||= begin
158
+ Entitlements.logger.debug "Mirroring #{ou_key} from #{cfg_obj['mirror']}"
159
+
160
+ unless @groups_in_ou_cache[cfg_obj["mirror"]]
161
+ raise "Cannot read_mirror on #{ou_key.inspect} because read_all has not occurred on #{cfg_obj['mirror'].inspect}!"
162
+ end
163
+
164
+ result = Set.new
165
+ @groups_in_ou_cache[cfg_obj["mirror"]].each do |source_dn|
166
+ source_group = @groups_cache[source_dn]
167
+ unless source_group
168
+ raise "No group has been calculated for #{source_dn.inspect}!"
169
+ end
170
+
171
+ new_dn = ["cn=#{source_group.cn}", cfg_obj["base"]].join(",")
172
+ @groups_cache[new_dn] ||= source_group.copy_of(new_dn)
173
+ result.add new_dn
174
+ end
175
+
176
+ result
177
+ end
178
+ end
179
+
180
+ # Construct the ruleset object for a given filename.
181
+ #
182
+ # filename - A String with the filename.
183
+ #
184
+ # Returns an Entitlements::Data::Groups::Calculated::* object.
185
+ Contract C::KeywordArgs[
186
+ filename: String,
187
+ config: C::HashOf[String => C::Any],
188
+ options: C::Optional[C::HashOf[Symbol => C::Any]]
189
+ ] => C::Or[
190
+ Entitlements::Data::Groups::Calculated::Ruby,
191
+ Entitlements::Data::Groups::Calculated::Text,
192
+ Entitlements::Data::Groups::Calculated::YAML,
193
+ ]
194
+ def self.ruleset(filename:, config:, options: {})
195
+ unless filename =~ /\.(\w+)\z/
196
+ raise ArgumentError, "Unable to determine the extension on #{filename.inspect}!"
197
+ end
198
+ ext = Regexp.last_match(1)
199
+
200
+ unless FILE_EXTENSIONS[ext]
201
+ Entitlements.logger.fatal "Unable to map filename #{filename.inspect} to a ruleset object!"
202
+ raise ArgumentError, "Unable to map filename #{filename.inspect} to a ruleset object!"
203
+ end
204
+
205
+ if config.key?("allowed_types")
206
+ unless config["allowed_types"].is_a?(Array)
207
+ Entitlements.logger.fatal "Configuration error: allowed_types should be an Array, got #{config['allowed_types'].inspect}"
208
+ raise ArgumentError, "Configuration error: allowed_types should be an Array, got #{config['allowed_types'].class}!"
209
+ end
210
+
211
+ unless config["allowed_types"].include?(ext)
212
+ allowed_join = config["allowed_types"].join(",")
213
+ Entitlements.logger.fatal "Files with extension #{ext.inspect} are not allowed in this OU! Allowed: #{allowed_join}!"
214
+ raise ArgumentError, "Files with extension #{ext.inspect} are not allowed in this OU! Allowed: #{allowed_join}!"
215
+ end
216
+ end
217
+
218
+ clazz = Kernel.const_get(FILE_EXTENSIONS[ext])
219
+ clazz.new(filename: filename, config: config, options: options)
220
+ end
221
+
222
+ #########################
223
+ # This section is handled as a class variable not an instance variable because rule definitions
224
+ # are global throughout the program.
225
+ #########################
226
+
227
+ @rules_index = {
228
+ "group" => Entitlements::Data::Groups::Calculated::Rules::Group,
229
+ "username" => Entitlements::Data::Groups::Calculated::Rules::Username
230
+ }
231
+
232
+ @filters_index = {}
233
+
234
+ # Retrieve the current rules index from the class.
235
+ #
236
+ # Takes no arguments.
237
+ #
238
+ # Returns a Hash.
239
+ Contract C::None => C::HashOf[String => Class]
240
+ def self.rules_index
241
+ @rules_index
242
+ end
243
+
244
+ # Retrieve the current filters index from the class.
245
+ #
246
+ # Takes no arguments.
247
+ #
248
+ # Returns a Hash.
249
+ Contract C::None => C::HashOf[String => Object]
250
+ def self.filters_index
251
+ @filters_index
252
+ end
253
+
254
+ # Retrieve the filters in the format used as default / starting point in other parts
255
+ # of the program: { "filter_name_1" => :none, "filter_name_2" => :none }
256
+ #
257
+ # Takes no arguments.
258
+ #
259
+ # Returns a Hash in the indicated format.
260
+ def self.filters_default
261
+ @filters_index.map { |k, _| [k, :none] }.to_h
262
+ end
263
+
264
+ # Register a rule (requires namespace and class references). Methods are registered
265
+ # per rule, not per instantiation.
266
+ #
267
+ # rule_name - String with the rule name.
268
+ # clazz - Class that implements the rule.
269
+ #
270
+ # Returns the class that implements the rule.
271
+ Contract String, Class => Class
272
+ def self.register_rule(rule_name, clazz)
273
+ @rules_index[rule_name] = clazz
274
+ end
275
+
276
+ # Register a filter (requires namespace and object references). Named filters are instantiated
277
+ # objects. It's possible to have multiple instantiations of the same class of filter.
278
+ #
279
+ # filter_name - String with the filter name.
280
+ # filter_cfg - Hash with a configuration for the filter.
281
+ #
282
+ # Returns the configuration of the filter object (a hash).
283
+ Contract String, C::HashOf[Symbol => Object] => C::HashOf[Symbol => Object]
284
+ def self.register_filter(filter_name, filter_cfg)
285
+ @filters_index[filter_name] = filter_cfg
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "groups/cached"
4
+ require_relative "groups/calculated"
5
+
6
+ module Entitlements
7
+ class Data
8
+ class Groups
9
+ class DuplicateGroupError < RuntimeError; end
10
+ class GroupNotFoundError < RuntimeError; end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Entitlements
6
+ class Data
7
+ class People
8
+ class Combined
9
+ include ::Contracts::Core
10
+ C = ::Contracts
11
+
12
+ PARAMETERS = {
13
+ "operator" => { required: true, type: String },
14
+ "components" => { required: true, type: Array },
15
+ }
16
+
17
+ # Fingerprint for the object based on unique parameters from the group configuration. If the fingerprint
18
+ # matches the same object should be re-used. This will raise an error if insufficient configuration is
19
+ # given.
20
+ #
21
+ # config - Hash of configuration values as may be found in the Entitlements configuration file.
22
+ #
23
+ # Returns a String with the "fingerprint" for this configuration.
24
+ Contract C::HashOf[String => C::Any] => String
25
+ def self.fingerprint(config)
26
+ # Fingerprint of the combined provider is the fingerprint of each constitutent provider. Then serialize
27
+ # to JSON to account for the and/or operator that is part of this configuration. Note: this method might
28
+ # end up being called recursively depending on the complexity of the combined configuration.
29
+ fingerprints = config["components"].map do |component|
30
+ Entitlements::Data::People.class_for_config(component).fingerprint(component.fetch("config"))
31
+ end
32
+
33
+ JSON.generate(config.fetch("operator") => fingerprints)
34
+ end
35
+
36
+ # Construct this object based on parameters in a group configuration. This is the direct translation
37
+ # between the Entitlements configuration file (which is always a Hash with configuration values) and
38
+ # the object constructed from this class (which can have whatever structure makes sense).
39
+ #
40
+ # config - Hash of configuration values as may be found in the Entitlements configuration file.
41
+ #
42
+ # Returns Entitlements::Data::People::Combined object.
43
+ # :nocov:
44
+ Contract C::HashOf[String => C::Any] => Entitlements::Data::People::Combined
45
+ def self.new_from_config(config)
46
+ new(
47
+ operator: config.fetch("operator"),
48
+ components: config.fetch("components")
49
+ )
50
+ end
51
+ # :nocov:
52
+
53
+ # Validate configuration options.
54
+ #
55
+ # key - String with the name of the data source.
56
+ # config - Hash with the configuration data.
57
+ #
58
+ # Returns nothing.
59
+ Contract String, C::HashOf[String => C::Any] => nil
60
+ def self.validate_config!(key, config)
61
+ text = "Combined people configuration for data source #{key.inspect}"
62
+ Entitlements::Util::Util.validate_attr!(PARAMETERS, config, text)
63
+
64
+ unless %w[and or].include?(config["operator"])
65
+ raise ArgumentError, "In #{key}, expected 'operator' to be either 'and' or 'or', not #{config['operator'].inspect}!"
66
+ end
67
+
68
+ component_spec = {
69
+ "config" => { required: true, type: Hash },
70
+ "name" => { required: false, type: String },
71
+ "type" => { required: true, type: String },
72
+ }
73
+
74
+ if config["components"].empty?
75
+ raise ArgumentError, "In #{key}, the array of components cannot be empty!"
76
+ end
77
+
78
+ config["components"].each do |component|
79
+ if component.is_a?(Hash)
80
+ component_name = component.fetch("name", component.inspect)
81
+ component_text = "Combined people configuration #{key.inspect} component #{component_name}"
82
+ Entitlements::Util::Util.validate_attr!(component_spec, component, component_text)
83
+ clazz = Entitlements::Data::People.class_for_config(component)
84
+ clazz.validate_config!("#{key}:#{component_name}", component.fetch("config"))
85
+ elsif component.is_a?(String)
86
+ if Entitlements.config.fetch("people", {}).fetch(component, nil)
87
+ resolved_component = Entitlements.config["people"][component]
88
+ clazz = Entitlements::Data::People.class_for_config(resolved_component)
89
+ clazz.validate_config!(component, resolved_component.fetch("config"))
90
+ else
91
+ raise ArgumentError, "In #{key}, reference to invalid component #{component.inspect}!"
92
+ end
93
+ else
94
+ raise ArgumentError, "In #{key}, expected array of hashes/strings but got #{component.inspect}!"
95
+ end
96
+ end
97
+
98
+ nil
99
+ end
100
+
101
+ # Constructor.
102
+ #
103
+ Contract C::KeywordArgs[
104
+ operator: String,
105
+ components: C::ArrayOf[C::HashOf[String => C::Any]]
106
+ ] => C::Any
107
+ def initialize(operator:, components:)
108
+ @combined = { operator: operator }
109
+ @combined[:components] = components.map do |component|
110
+ clazz = Entitlements::Data::People.class_for_config(component)
111
+ clazz.new_from_config(component["config"])
112
+ end
113
+ @people = nil
114
+ end
115
+
116
+ # Read in the people from a combined provider. Cache result for later access.
117
+ #
118
+ # uid - Optionally a uid to return. If not specified, returns the entire hash.
119
+ #
120
+ # Returns Hash of { uid => Entitlements::Models::Person } or one Entitlements::Models::Person.
121
+ Contract C::Maybe[String] => C::Or[Entitlements::Models::Person, C::HashOf[String => Entitlements::Models::Person]]
122
+ def read(uid = nil)
123
+ @people ||= read_entire_hash
124
+ return @people unless uid
125
+
126
+ @people_downcase ||= @people.map { |people_uid, _data| [people_uid.downcase, people_uid] }.to_h
127
+ unless @people_downcase.key?(uid.downcase)
128
+ raise Entitlements::Data::People::NoSuchPersonError, "read(#{uid.inspect}) matched no known person"
129
+ end
130
+
131
+ @people[@people_downcase[uid.downcase]]
132
+ end
133
+
134
+ private
135
+
136
+ # Read an entire hash from the combined data source.
137
+ #
138
+ # Takes no arguments.
139
+ #
140
+ # Returns Hash of { uid => Entitlements::Models::Person }.
141
+ Contract C::None => C::HashOf[String => Entitlements::Models::Person]
142
+ def read_entire_hash
143
+ # @combined[:operator] is "or" or "and". Call the "read" method on each component and then assemble the
144
+ # results according to the specified logic. When a user is seen more than once, deconflict by using the *first*
145
+ # constructed person model that we have seen.
146
+ data = @combined[:components].map { |component| component.read }
147
+
148
+ result = {}
149
+ data.each do |data_hash|
150
+ data_hash.each do |user, user_data|
151
+ result[user] ||= user_data
152
+ end
153
+ end
154
+
155
+ if @combined[:operator] == "and"
156
+ users = Set.new(common_keys(data))
157
+ result.select! { |k, _| users.member?(k) }
158
+ end
159
+
160
+ result
161
+ end
162
+
163
+ # Given an arbitrary number of hashes, return the keys that are common in all of them.
164
+ # (hash1_keys & hash2_keys & hash3_keys)
165
+ #
166
+ # hashes - An array of hashes in which to find common keys.
167
+ #
168
+ # Returns an array of common elements.
169
+ Contract C::ArrayOf[Hash] => C::SetOf[C::Any]
170
+ def common_keys(hashes)
171
+ return Set.new if hashes.empty?
172
+
173
+ hash1 = hashes.shift
174
+ result = Set.new(hash1.keys)
175
+ hashes.each { |h| result = result & h.keys }
176
+ result
177
+ end
178
+
179
+ # Given an arbitrary number of hashes, return all keys seen in any of them.
180
+ # (hash1_keys | hash2_keys | hash3_keys)
181
+ #
182
+ # arrays - An array of arrays with elements in which to find any elements.
183
+ #
184
+ # Returns an array of all elements.
185
+ Contract C::ArrayOf[Hash] => C::SetOf[C::Any]
186
+ def all_keys(hashes)
187
+ return Set.new if hashes.empty?
188
+
189
+ hash1 = hashes.shift
190
+ result = Set.new(hash1.keys)
191
+ hashes.each { |h| result.merge(h.keys) }
192
+ result
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Entitlements
4
+ class Data
5
+ class People
6
+ class Dummy
7
+ include ::Contracts::Core
8
+ C = ::Contracts
9
+
10
+ # :nocov:
11
+
12
+ # Fingerprint for the object based on unique parameters from the group configuration. If the fingerprint
13
+ # matches the same object should be re-used. This will raise an error if insufficient configuration is
14
+ # given.
15
+ #
16
+ # config - Hash of configuration values as may be found in the Entitlements configuration file.
17
+ #
18
+ # Returns a String with the "fingerprint" for this configuration.
19
+ Contract C::HashOf[String => C::Any] => String
20
+ def self.fingerprint(_config)
21
+ "dummy"
22
+ end
23
+
24
+ # Construct this object based on parameters in a group configuration. This is the direct translation
25
+ # between the Entitlements configuration file (which is always a Hash with configuration values) and
26
+ # the object constructed from this class (which can have whatever structure makes sense).
27
+ #
28
+ # config - Hash of configuration values as may be found in the Entitlements configuration file.
29
+ #
30
+ # Returns Entitlements::Data::People::LDAP object.
31
+ Contract C::HashOf[String => C::Any] => Entitlements::Data::People::Dummy
32
+ def self.new_from_config(_config)
33
+ new
34
+ end
35
+
36
+ # Validate configuration options.
37
+ #
38
+ # key - String with the name of the data source.
39
+ # config - Hash with the configuration data.
40
+ #
41
+ # Returns nothing.
42
+ Contract String, C::HashOf[String => C::Any] => nil
43
+ def self.validate_config!(_key, _config)
44
+ # This is always valid.
45
+ end
46
+
47
+ # Constructor.
48
+ #
49
+ # Takes no arguments.
50
+ Contract C::None => C::Any
51
+ def initialize
52
+ # This is pretty boring.
53
+ end
54
+
55
+ # This would normally read in all people and then return the hash or a specific person.
56
+ # In this case the hash is empty and there are no people.
57
+ #
58
+ # dn - Optionally a DN to return. If not specified, returns the entire hash.
59
+ #
60
+ # Returns empty hash or raises an error.
61
+ Contract C::Maybe[String] => C::Or[C::HashOf[String => Entitlements::Models::Person], Entitlements::Models::Person]
62
+ def read(dn = nil)
63
+ return {} if dn.nil?
64
+ raise Entitlements::Data::People::NoSuchPersonError, "read(#{dn.inspect}) matched no known person"
65
+ end
66
+
67
+ # :nocov:
68
+ end
69
+ end
70
+ end
71
+ end