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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e129c20434bdad80c9fb7395a3a18f787f3a6c3c7c5579254a2bcd1b422e2715
|
4
|
+
data.tar.gz: eee34c03695d06d79c5eec2dc8e98627a0c66e5f9c4425c60d1ba247f9541f22
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fc823d7ff8a6c1c6cdc582526e5bd4f3fa67d48ae6acc9940c37e15073c6661ca33469b85e5e12e4e6dee7981b4ef3022225c0697b1e449b1c8f4a98f524f9cf
|
7
|
+
data.tar.gz: 44724ff2988f4a7ca36b20489ace98d0af6351e8214d2975a1e256cfc088a5a4070c91524ed49342e684d5edc7bc7eb41d59dba8788000dfefef8566b653d3bb
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.7
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
ENV["BUNDLE_GEMFILE"] = File.expand_path("../Gemfile", File.dirname(__FILE__))
|
4
|
+
require "bundler/setup"
|
5
|
+
require "contracts"
|
6
|
+
|
7
|
+
# We don't need Contract outside of normal development
|
8
|
+
VALID = [true, nil]
|
9
|
+
class Contract
|
10
|
+
def self.valid?(arg, contract)
|
11
|
+
VALID
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require "entitlements"
|
16
|
+
exitcode = Entitlements::Cli.run
|
17
|
+
exitcode ||= 0
|
18
|
+
exit exitcode
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class provides common methods and is intended to be inherited by other audit providers.
|
4
|
+
|
5
|
+
module Entitlements
|
6
|
+
class Auditor
|
7
|
+
class Base
|
8
|
+
include ::Contracts::Core
|
9
|
+
C = ::Contracts
|
10
|
+
|
11
|
+
attr_reader :description, :provider_id
|
12
|
+
|
13
|
+
# ---------
|
14
|
+
# Interface
|
15
|
+
# ---------
|
16
|
+
|
17
|
+
# Constructor.
|
18
|
+
#
|
19
|
+
# config - A Hash with configuration options
|
20
|
+
Contract Logger, C::HashOf[String => C::Any] => C::Any
|
21
|
+
def initialize(logger, config)
|
22
|
+
@logger = logger
|
23
|
+
@description = config["description"] || self.class.to_s
|
24
|
+
@provider_id = config["provider_id"] || self.class.to_s.split("::").last
|
25
|
+
@config = config
|
26
|
+
end
|
27
|
+
|
28
|
+
# Setup. This sets up the audit provider before any action takes place. This may be
|
29
|
+
# declared in the child class.
|
30
|
+
#
|
31
|
+
# Takes no arguments.
|
32
|
+
#
|
33
|
+
# Returns nothing.
|
34
|
+
Contract C::None => nil
|
35
|
+
def setup
|
36
|
+
# :nocov:
|
37
|
+
nil
|
38
|
+
# :nocov:
|
39
|
+
end
|
40
|
+
|
41
|
+
# Commit. This takes the entirety of group objects and actions and records them in
|
42
|
+
# whatever methodology the audit provider uses.
|
43
|
+
#
|
44
|
+
# actions - Array of Entitlements::Models::Action (all requested actions)
|
45
|
+
# successful_actions - Array of Entitlements::Models::Action (successfully applied actions)
|
46
|
+
# provider_exception - Exception raised by a provider when applying (hopefully nil)
|
47
|
+
#
|
48
|
+
# Returns nothing.
|
49
|
+
Contract C::KeywordArgs[
|
50
|
+
actions: C::ArrayOf[Entitlements::Models::Action],
|
51
|
+
successful_actions: C::ArrayOf[Entitlements::Models::Action],
|
52
|
+
provider_exception: C::Or[nil, Exception]
|
53
|
+
] => nil
|
54
|
+
def commit(actions:, successful_actions:, provider_exception:)
|
55
|
+
# :nocov:
|
56
|
+
nil
|
57
|
+
# :nocov:
|
58
|
+
end
|
59
|
+
|
60
|
+
# Set up a logger class that wraps incoming messages with the prefix and (if meaningful) the
|
61
|
+
# provider ID. Messages are then sent with the requested priority to the actual logger object.
|
62
|
+
class CustomLogger
|
63
|
+
def initialize(underlying_object, underlying_logger)
|
64
|
+
@underlying_object = underlying_object
|
65
|
+
@underlying_logger = underlying_logger
|
66
|
+
end
|
67
|
+
|
68
|
+
def prefix
|
69
|
+
@prefix ||= begin
|
70
|
+
if @underlying_object.provider_id == @underlying_object.class.to_s.split("::").last
|
71
|
+
@underlying_object.class.to_s
|
72
|
+
else
|
73
|
+
"#{@underlying_object.class}[#{@underlying_object.provider_id}]"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def method_missing(m, *args, &block)
|
79
|
+
args[0] = "#{prefix}: #{args.first}"
|
80
|
+
@underlying_logger.send(m, *args, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
attr_reader :config
|
87
|
+
|
88
|
+
# Intercept calls to logger to wrap through the custom class.
|
89
|
+
def logger
|
90
|
+
@logger_class ||= CustomLogger.new(self, @logger)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Raise a configuration error message.
|
94
|
+
#
|
95
|
+
# message - A String with the error message to be logged and raised.
|
96
|
+
#
|
97
|
+
# Returns nothing because it raises an error.
|
98
|
+
Contract String => C::Any
|
99
|
+
def configuration_error(message)
|
100
|
+
provider = self.class.to_s.split("::").last
|
101
|
+
error_message = "Configuration error for provider=#{provider} id=#{provider_id}: #{message}"
|
102
|
+
logger.fatal "Configuration error: #{message}"
|
103
|
+
raise ArgumentError, error_message
|
104
|
+
end
|
105
|
+
|
106
|
+
# Require the the configuration contain certain keys (no validation is performed on the
|
107
|
+
# values - just make sure the key exists).
|
108
|
+
#
|
109
|
+
# required_keys - An Array of Strings with the required keys.
|
110
|
+
#
|
111
|
+
# Returns nothing.
|
112
|
+
Contract C::ArrayOf[String] => nil
|
113
|
+
def require_config_keys(required_keys)
|
114
|
+
missing_keys = required_keys - config.keys
|
115
|
+
return unless missing_keys.any?
|
116
|
+
configuration_error "Not all required keys are defined. Missing: #{missing_keys.join(',')}."
|
117
|
+
end
|
118
|
+
|
119
|
+
# Convert a distinguished name (cn=something,ou=foo,dc=example,dc=net) to a file
|
120
|
+
# path (dc=net/dc=example/ou=foo/cn=something).
|
121
|
+
#
|
122
|
+
# dn - A String with the distinguished name.
|
123
|
+
#
|
124
|
+
# Returns a String with the path.
|
125
|
+
Contract String => String
|
126
|
+
def path_from_dn(dn)
|
127
|
+
File.join(dn.split(",").reverse)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Convert a path (dc=net/dc=example/ou=foo/cn=something) to a distinguished name
|
131
|
+
# (cn=something,ou=foo,dc=example,dc=net).
|
132
|
+
#
|
133
|
+
# path - A String with the path name.
|
134
|
+
#
|
135
|
+
# Returns a String with the distinguished name.
|
136
|
+
Contract String => String
|
137
|
+
def dn_from_path(path)
|
138
|
+
path.split("/").reject { |i| i.empty? }.reverse.join(",")
|
139
|
+
end
|
140
|
+
|
141
|
+
# From a list of actions, return only the ones that have a net change in membership.
|
142
|
+
# In other words, filter out the ones where only metadata / description / etc. has changed.
|
143
|
+
#
|
144
|
+
# actions - Incoming array of Entitlements::Models::Action
|
145
|
+
#
|
146
|
+
# Returns an array of Entitlements::Models::Action
|
147
|
+
Contract C::ArrayOf[Entitlements::Models::Action] => C::ArrayOf[Entitlements::Models::Action]
|
148
|
+
def actions_with_membership_change(actions)
|
149
|
+
actions.select do |action|
|
150
|
+
if action.updated.is_a?(Entitlements::Models::Person) || action.existing == :none
|
151
|
+
# MemberOf or other modification to the person itself, not handled by this auditor
|
152
|
+
false
|
153
|
+
elsif action.updated.nil? || action.existing.nil?
|
154
|
+
# Add/remove group always triggers a commit
|
155
|
+
true
|
156
|
+
else
|
157
|
+
action.updated.member_strings_insensitive != action.existing.member_strings_insensitive
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# To add a new backend, make its "Controller" class inherit from Entitlements::Backend::BaseController.
|
4
|
+
# Consider using dummy/controller.rb as a template for a brand new class.
|
5
|
+
|
6
|
+
# Needed to register backends
|
7
|
+
require_relative "../cli"
|
8
|
+
require_relative "../util/util"
|
9
|
+
|
10
|
+
module Entitlements
|
11
|
+
class Backend
|
12
|
+
class BaseController
|
13
|
+
include ::Contracts::Core
|
14
|
+
C = ::Contracts
|
15
|
+
|
16
|
+
# Upon loading of the class itself, register the class in the list of available
|
17
|
+
# backends that is tracked in the Entitlements class.
|
18
|
+
def self.register
|
19
|
+
Entitlements.register_backend(identifier, self, priority)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Default priority is 10 - override by defining this method in the child class.
|
23
|
+
def self.priority
|
24
|
+
10
|
25
|
+
end
|
26
|
+
|
27
|
+
# :nocov:
|
28
|
+
def priority
|
29
|
+
self.class.priority
|
30
|
+
end
|
31
|
+
# :nocov:
|
32
|
+
|
33
|
+
# Default identifier is the de-camelized name of the class - override by defining this method in the child class.
|
34
|
+
def self.identifier
|
35
|
+
classname = self.to_s.split("::")[-2]
|
36
|
+
Entitlements::Util::Util.decamelize(classname)
|
37
|
+
end
|
38
|
+
|
39
|
+
COMMON_GROUP_CONFIG = {
|
40
|
+
"allowed_methods" => { required: false, type: Array },
|
41
|
+
"allowed_types" => { required: false, type: Array },
|
42
|
+
"dir" => { required: false, type: String }
|
43
|
+
}
|
44
|
+
|
45
|
+
# Constructor. Generic constructor that takes a hash of configuration options.
|
46
|
+
#
|
47
|
+
# group_name - Name of the corresponding group in the entitlements configuration file.
|
48
|
+
# config - Optionally, a Hash of configuration information (configuration is referenced if empty).
|
49
|
+
Contract String, C::Maybe[C::HashOf[String => C::Any]] => C::Any
|
50
|
+
def initialize(group_name, config = nil)
|
51
|
+
@group_name = group_name
|
52
|
+
@config = config ? config.dup : Entitlements.config["groups"].fetch(group_name).dup
|
53
|
+
@config.delete("type")
|
54
|
+
@actions = []
|
55
|
+
@logger = Entitlements.logger
|
56
|
+
validate_config!(@group_name, @config)
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :actions
|
60
|
+
|
61
|
+
# Print difference array.
|
62
|
+
#
|
63
|
+
# key - String with the key identifying the OU
|
64
|
+
# added - Array[Entitlements::Models::Action]
|
65
|
+
# removed - Array[Entitlements::Models::Action]
|
66
|
+
# changed - Array[Entitlements::Models::Action]
|
67
|
+
# ignored_users - Optionally a Set of Strings with usernames to ignore
|
68
|
+
#
|
69
|
+
# Returns nothing (this just prints to logger).
|
70
|
+
Contract C::KeywordArgs[
|
71
|
+
key: String,
|
72
|
+
added: C::ArrayOf[Entitlements::Models::Action],
|
73
|
+
removed: C::ArrayOf[Entitlements::Models::Action],
|
74
|
+
changed: C::ArrayOf[Entitlements::Models::Action],
|
75
|
+
ignored_users: C::Maybe[C::SetOf[String]]
|
76
|
+
] => C::Any
|
77
|
+
def print_differences(key:, added:, removed:, changed:, ignored_users: Set.new)
|
78
|
+
added_array = added.map { |i| [i.dn, :added, i] }
|
79
|
+
removed_array = removed.map { |i| [i.dn, :removed, i] }
|
80
|
+
changed_array = changed.map { |i| [i.dn, :changed, i] }
|
81
|
+
|
82
|
+
combined = (added_array + removed_array + changed_array).sort_by { |i| i.first.to_s.downcase }
|
83
|
+
combined.each do |entry|
|
84
|
+
identifier = entry[0]
|
85
|
+
changetype = entry[1]
|
86
|
+
obj = entry[2]
|
87
|
+
|
88
|
+
if changetype == :added
|
89
|
+
members = obj.updated.member_strings.map { |i| i =~ /\Auid=(.+?),/ ? Regexp.last_match(1) : i }
|
90
|
+
Entitlements.logger.info "ADD #{identifier} to #{key} (Members: #{members.sort.join(',')})"
|
91
|
+
elsif changetype == :removed
|
92
|
+
Entitlements.logger.info "DELETE #{identifier} from #{key}"
|
93
|
+
else
|
94
|
+
ignored_users.merge obj.ignored_users
|
95
|
+
existing_members = obj.existing.member_strings
|
96
|
+
Entitlements::Util::Util.remove_uids(existing_members, ignored_users)
|
97
|
+
|
98
|
+
proposed_members = obj.updated.member_strings
|
99
|
+
Entitlements::Util::Util.remove_uids(proposed_members, ignored_users)
|
100
|
+
|
101
|
+
added_to_group = (proposed_members - existing_members).map { |i| [i, "+"] }
|
102
|
+
removed_from_group = (existing_members - proposed_members).map { |i| [i, "-"] }
|
103
|
+
|
104
|
+
# Filter out case-only differences. For example if "bob" is in existing and "BOB" is in proposed,
|
105
|
+
# we don't want to show this as a difference.
|
106
|
+
downcase_proposed_members = proposed_members.map { |m| m.downcase }
|
107
|
+
downcase_existing_members = existing_members.map { |m| m.downcase }
|
108
|
+
duplicated = downcase_proposed_members & downcase_existing_members
|
109
|
+
added_to_group.reject! { |m| duplicated.include?(m.first.downcase) }
|
110
|
+
removed_from_group.reject! { |m| duplicated.include?(m.first.downcase) }
|
111
|
+
|
112
|
+
# What's left is actual changes.
|
113
|
+
combined_group = (added_to_group + removed_from_group).sort_by { |i| i.first.downcase }
|
114
|
+
if combined_group.any?
|
115
|
+
Entitlements.logger.info "CHANGE #{identifier} in #{key}"
|
116
|
+
combined_group.each do |item, item_changetype|
|
117
|
+
Entitlements.logger.info ". #{item_changetype} #{item}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if obj.existing.description != obj.updated.description && obj.ou_type == "ldap"
|
122
|
+
Entitlements.logger.info "METADATA CHANGE #{identifier} in #{key}"
|
123
|
+
Entitlements.logger.info "- Old description: #{obj.existing.description.inspect}"
|
124
|
+
Entitlements.logger.info "+ New description: #{obj.updated.description.inspect}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get count of changes.
|
131
|
+
#
|
132
|
+
# Takes no arguments.
|
133
|
+
#
|
134
|
+
# Returns an Integer.
|
135
|
+
Contract C::None => Integer
|
136
|
+
def change_count
|
137
|
+
actions.size
|
138
|
+
end
|
139
|
+
|
140
|
+
# Stub methods
|
141
|
+
# :nocov:
|
142
|
+
def prefetch
|
143
|
+
# Can be left undefined
|
144
|
+
end
|
145
|
+
|
146
|
+
def validate
|
147
|
+
# Can be left undefined
|
148
|
+
end
|
149
|
+
|
150
|
+
def calculate
|
151
|
+
raise "Must be defined in child class"
|
152
|
+
end
|
153
|
+
|
154
|
+
def preapply
|
155
|
+
# Can be left undefined
|
156
|
+
end
|
157
|
+
|
158
|
+
def apply(action)
|
159
|
+
raise "Must be defined in child class"
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_config!(key, data)
|
163
|
+
# Can be left undefined (but really shouldn't)
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
attr_reader :config, :group_name, :logger
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module Entitlements
|
6
|
+
class Backend
|
7
|
+
class BaseProvider
|
8
|
+
include ::Contracts::Core
|
9
|
+
C = ::Contracts
|
10
|
+
|
11
|
+
# Dry run of committing changes. Returns a list of users added or removed.
|
12
|
+
# Takes a group; looks up that same group in the appropriate backend.
|
13
|
+
#
|
14
|
+
# group - An Entitlements::Models::Group object.
|
15
|
+
# ignored_users - Optionally, a Set of lower-case Strings of users to ignore.
|
16
|
+
#
|
17
|
+
# Returns added / removed hash.
|
18
|
+
Contract Entitlements::Models::Group, C::Maybe[C::SetOf[String]] => Hash[added: C::SetOf[String], removed: C::SetOf[String]]
|
19
|
+
def diff(group, ignored_users = Set.new)
|
20
|
+
existing_group = read(group.cn.downcase)
|
21
|
+
return diff_existing_updated(existing_group, group, ignored_users)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Dry run of committing changes. Returns a list of users added or removed.
|
25
|
+
# Takes an existing and an updated group object, avoiding a lookup in the backend.
|
26
|
+
#
|
27
|
+
# existing_group - An Entitlements::Models::Group object.
|
28
|
+
# group - An Entitlements::Models::Group object.
|
29
|
+
# ignored_users - Optionally, a Set of lower-case Strings of users to ignore.
|
30
|
+
Contract Entitlements::Models::Group, Entitlements::Models::Group, C::Maybe[C::SetOf[String]] => Hash[added: C::SetOf[String], removed: C::SetOf[String]]
|
31
|
+
def diff_existing_updated(existing_group, group, ignored_users = Set.new)
|
32
|
+
# The comparison needs to be done case-insensitive because some backends (e.g. GitHub organizations or teams)
|
33
|
+
# may report members with different capitalization than is used in Entitlements. Keep track of correct capitalization
|
34
|
+
# of member names here so they can be applied later. Note that `group` (from Entitlements) overrides `existing_group`
|
35
|
+
# (from the backend).
|
36
|
+
member_with_correct_capitalization = existing_group.member_strings.map { |ms| [ms.downcase, ms] }.to_h
|
37
|
+
member_with_correct_capitalization.merge! group.member_strings.map { |ms| [ms.downcase, ms] }.to_h
|
38
|
+
|
39
|
+
existing_members = existing_group.member_strings.map { |u| u.downcase }
|
40
|
+
Entitlements::Util::Util.remove_uids(existing_members, ignored_users)
|
41
|
+
|
42
|
+
proposed_members = group.member_strings.map { |u| u.downcase }
|
43
|
+
Entitlements::Util::Util.remove_uids(proposed_members, ignored_users)
|
44
|
+
|
45
|
+
added_members = proposed_members - existing_members
|
46
|
+
removed_members = existing_members - proposed_members
|
47
|
+
|
48
|
+
{
|
49
|
+
added: Set.new(added_members.map { |ms| member_with_correct_capitalization[ms] }),
|
50
|
+
removed: Set.new(removed_members.map { |ms| member_with_correct_capitalization[ms] })
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Entitlements
|
4
|
+
class Backend
|
5
|
+
class Dummy
|
6
|
+
class Controller < Entitlements::Backend::BaseController
|
7
|
+
register
|
8
|
+
|
9
|
+
# :nocov:
|
10
|
+
include ::Contracts::Core
|
11
|
+
C = ::Contracts
|
12
|
+
|
13
|
+
# Pre-fetch the existing group membership in each OU.
|
14
|
+
#
|
15
|
+
# Takes no arguments.
|
16
|
+
#
|
17
|
+
# Returns nothing. (Populates cache.)
|
18
|
+
Contract C::None => C::Any
|
19
|
+
def prefetch
|
20
|
+
# This does nothing.
|
21
|
+
end
|
22
|
+
|
23
|
+
# Validation routines.
|
24
|
+
#
|
25
|
+
# Takes no arguments.
|
26
|
+
#
|
27
|
+
# Returns nothing. (Populates cache.)
|
28
|
+
Contract C::None => C::Any
|
29
|
+
def validate
|
30
|
+
# This does nothing.
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get count of changes.
|
34
|
+
#
|
35
|
+
# Takes no arguments.
|
36
|
+
#
|
37
|
+
# Returns an Integer.
|
38
|
+
Contract C::None => Integer
|
39
|
+
def change_count
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calculation routines.
|
44
|
+
#
|
45
|
+
# Takes no arguments.
|
46
|
+
#
|
47
|
+
# Returns nothing (populates @actions).
|
48
|
+
Contract C::None => C::Any
|
49
|
+
def calculate
|
50
|
+
# No point in calculating anything. Any references herein will be calculated automatically.
|
51
|
+
@actions = []
|
52
|
+
end
|
53
|
+
|
54
|
+
# Pre-apply routines.
|
55
|
+
#
|
56
|
+
# Takes no arguments.
|
57
|
+
#
|
58
|
+
# Returns nothing.
|
59
|
+
Contract C::None => C::Any
|
60
|
+
def preapply
|
61
|
+
# This does nothing.
|
62
|
+
end
|
63
|
+
|
64
|
+
# Apply changes.
|
65
|
+
#
|
66
|
+
# action - Action array.
|
67
|
+
#
|
68
|
+
# Returns nothing.
|
69
|
+
Contract Entitlements::Models::Action => C::Any
|
70
|
+
def apply(caction)
|
71
|
+
# This does nothing.
|
72
|
+
end
|
73
|
+
|
74
|
+
# Validate configuration options.
|
75
|
+
#
|
76
|
+
# key - String with the name of the group.
|
77
|
+
# data - Hash with the configuration data.
|
78
|
+
#
|
79
|
+
# Returns nothing.
|
80
|
+
Contract String, C::HashOf[String => C::Any] => nil
|
81
|
+
def validate_config!(key, data)
|
82
|
+
# Do nothing to validate. Pass whatever arguments you want, and this will just ignore them!
|
83
|
+
end
|
84
|
+
|
85
|
+
# :nocov:
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|