entitlements-app 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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