audiences 1.6.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/audiences-ujs.js +204 -1318
- data/app/controllers/audiences/contexts_controller.rb +4 -15
- data/app/controllers/audiences/scim_proxy_controller.rb +9 -11
- data/app/models/audiences/context.rb +41 -4
- data/app/models/audiences/context_extra_user.rb +8 -0
- data/app/models/audiences/criterion.rb +42 -8
- data/app/models/audiences/criterion_group.rb +8 -0
- data/app/models/audiences/external_user.rb +92 -14
- data/app/models/audiences/group.rb +27 -0
- data/app/models/audiences/group_membership.rb +14 -0
- data/db/migrate/20250520181229_create_audiences_groups.rb +17 -0
- data/db/migrate/20250521173148_add_scim_id_to_audiences_external_users.rb +8 -0
- data/db/migrate/20250521182852_create_audiences_group_memberships.rb +12 -0
- data/db/migrate/20250527203948_populate_external_users_scim_id.rb +15 -0
- data/db/migrate/20250528130640_add_display_name_to_audiences_external_users.rb +8 -0
- data/db/migrate/20250618184332_add_active_flag_to_external_users_and_groups.rb +8 -0
- data/db/migrate/20250620005528_create_audiences_criterion_groups.rb +12 -0
- data/db/migrate/20250620005800_rename_audiences_criterion_groups_to_groups_json.rb +7 -0
- data/db/migrate/20250623131202_move_group_criterion_to_criterion_groups.rb +27 -0
- data/db/migrate/20250624171247_create_audiences_context_extra_users.rb +12 -0
- data/db/migrate/20250624171706_rename_audiences_context_extra_users_to_extra_users_json.rb +7 -0
- data/db/migrate/20250701173946_move_extra_users_to_context_extra_users.rb +21 -0
- data/docs/CHANGELOG.md +17 -0
- data/docs/README.md +0 -11
- data/lib/audiences/configuration.rb +71 -45
- data/lib/audiences/editor_helper.rb +4 -1
- data/lib/audiences/engine.rb +19 -1
- data/lib/audiences/model.rb +9 -8
- data/lib/audiences/notifications.rb +4 -2
- data/lib/audiences/scim/field_mapping.rb +49 -0
- data/lib/audiences/scim/observer_base.rb +8 -0
- data/lib/audiences/scim/patch_groups_observer.rb +51 -0
- data/lib/audiences/scim/patch_op.rb +42 -0
- data/lib/audiences/scim/patch_users_observer.rb +38 -0
- data/lib/audiences/scim/scim_data.rb +31 -0
- data/lib/audiences/scim/upsert_groups_observer.rb +40 -0
- data/lib/audiences/scim/upsert_users_observer.rb +48 -0
- data/lib/audiences/scim.rb +9 -15
- data/lib/audiences/version.rb +1 -1
- data/lib/audiences.rb +5 -5
- metadata +40 -9
- data/app/jobs/audiences/application_job.rb +0 -6
- data/app/models/audiences/context_users.rb +0 -32
- data/app/models/audiences/criterion_users.rb +0 -28
- data/lib/audiences/railtie.rb +0 -12
- data/lib/audiences/scim/client.rb +0 -29
- data/lib/audiences/scim/resource.rb +0 -48
- data/lib/audiences/scim/resources_query.rb +0 -66
@@ -5,6 +5,63 @@ module Audiences
|
|
5
5
|
|
6
6
|
# Configuration options
|
7
7
|
|
8
|
+
# Sync groups and users with TwoPercent
|
9
|
+
config_accessor(:logger)
|
10
|
+
|
11
|
+
# Sync groups and users with TwoPercent
|
12
|
+
config_accessor(:observe_scim) { true }
|
13
|
+
|
14
|
+
# Group types that can form an audience
|
15
|
+
config_accessor :group_types do
|
16
|
+
%w[Groups]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Defines a default scope for users, so the users that are part of an audience can
|
20
|
+
# be filtered (i.e.: only active, only users in a specific group, etc)
|
21
|
+
#
|
22
|
+
# By default, only active users are listed.
|
23
|
+
#
|
24
|
+
# I.e.:
|
25
|
+
#
|
26
|
+
# # Allowing inactive users
|
27
|
+
# Audiences.configure do |config|
|
28
|
+
# config.default_users_scope = -> { all }
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Accepting only users in certain groups
|
32
|
+
# Audiences.configure do |config|
|
33
|
+
# config.default_users_scope = -> { includes(:groups).merge(Audiences::Group.where(scim_id: ALLOWED_GROUPS)) }
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# This configuration defaults to `-> { active }`
|
37
|
+
#
|
38
|
+
config_accessor :default_users_scope do
|
39
|
+
->(*) { active }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Defines a default scope for groups, so the groups that are part of an audience can
|
43
|
+
# be filtered (i.e.: only active, only specific groups, etc)
|
44
|
+
#
|
45
|
+
# By default, only active groups are listed.
|
46
|
+
#
|
47
|
+
# I.e.:
|
48
|
+
#
|
49
|
+
# # Allowing inactive groups
|
50
|
+
# Audiences.configure do |config|
|
51
|
+
# config.default_groups_scope = -> { all }
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# # Accepting only groups in certain groups
|
55
|
+
# Audiences.configure do |config|
|
56
|
+
# config.default_groups_scope = -> { where(scim_id: ALLOWED_GROUPS) }
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# This configuration defaults to `-> { active }`
|
60
|
+
#
|
61
|
+
config_accessor :default_groups_scope do
|
62
|
+
->(*) { active }
|
63
|
+
end
|
64
|
+
|
8
65
|
# These are the user attributes that will be exposed in the audiences endpoints.
|
9
66
|
# They're required by the UI to display the user information.
|
10
67
|
#
|
@@ -37,7 +94,20 @@ module Audiences
|
|
37
94
|
# end
|
38
95
|
#
|
39
96
|
config_accessor :authenticate do
|
40
|
-
->(*)
|
97
|
+
->(*) do
|
98
|
+
Audiences.logger.warn(<<~MESSAGE)
|
99
|
+
Audiences authenticate is currently configured using a default and is blocking authenticaiton.
|
100
|
+
|
101
|
+
To make this wraning go away provide a configuration for `Audiences.config.authenticate`.
|
102
|
+
|
103
|
+
The value should:
|
104
|
+
1. Be callable like a Proc.
|
105
|
+
2. Return true when the request is permitted.
|
106
|
+
3. Return false when the request is not permitted.
|
107
|
+
MESSAGE
|
108
|
+
|
109
|
+
false
|
110
|
+
end
|
41
111
|
end
|
42
112
|
|
43
113
|
#
|
@@ -51,50 +121,6 @@ module Audiences
|
|
51
121
|
#
|
52
122
|
config_accessor(:identity_key) { :id }
|
53
123
|
|
54
|
-
#
|
55
|
-
# SCIM service configurations. This should be a Hash containint, at least, the URI.
|
56
|
-
#
|
57
|
-
# I.e.:
|
58
|
-
#
|
59
|
-
# Audiences.configure do |config|
|
60
|
-
# config.scim = { uri: "http://localhost/api/scim" }
|
61
|
-
# end
|
62
|
-
#
|
63
|
-
# It can also contain HTTP headers, such as "Authorization":
|
64
|
-
#
|
65
|
-
# I.e.:
|
66
|
-
#
|
67
|
-
# Audiences.configure do |config|
|
68
|
-
# config.scim = {
|
69
|
-
# uri: "http://localhost/api/scim",
|
70
|
-
# headers: { "Authorization" => "Bearer auth-token" }
|
71
|
-
# }
|
72
|
-
# end
|
73
|
-
#
|
74
|
-
config_accessor :scim
|
75
|
-
|
76
|
-
#
|
77
|
-
# Resources defaults. Change this configuration via the `resource` helper.
|
78
|
-
# This configuration lists the current Audiences accessible resource defaults,
|
79
|
-
# and defaults to Users only. To add other resource types for criteria building.
|
80
|
-
#
|
81
|
-
# @see `resource`.
|
82
|
-
#
|
83
|
-
config_accessor :resources do
|
84
|
-
{ Users: Scim::Resource.new(type: :Users, attributes: ["active", { "photos" => %w[type value] }],
|
85
|
-
filter: "active eq true") }
|
86
|
-
end
|
87
|
-
|
88
|
-
#
|
89
|
-
# Configures a resource default.
|
90
|
-
#
|
91
|
-
# @param type [Symbol] the resource type in plural, as in scim (i.e.: :Users)
|
92
|
-
# @param attributes [String] the list of attributes to fetch for the resource (i.e.: "id,externalId,displayName")
|
93
|
-
# @see [Audiences::Scim::Resource]
|
94
|
-
def config.resource(type, **kwargs)
|
95
|
-
resources[type] = Scim::Resource.new(type: type, **kwargs)
|
96
|
-
end
|
97
|
-
|
98
124
|
#
|
99
125
|
# Notifications configurations.
|
100
126
|
# Within this block, you should be able to easily register job classes to execute as
|
@@ -3,12 +3,15 @@
|
|
3
3
|
module Audiences
|
4
4
|
module EditorHelper
|
5
5
|
def render_audiences_editor(context, html_class: "audiences-editor",
|
6
|
-
uri: Audiences::Engine.routes.url_helpers.root_path
|
6
|
+
uri: Audiences::Engine.routes.url_helpers.root_path,
|
7
|
+
allow_match_all: true, allow_individuals: true)
|
7
8
|
content_tag(:div, "",
|
8
9
|
data: {
|
9
10
|
react_class: "AudiencesEditor",
|
10
11
|
audiences_uri: uri,
|
11
12
|
audiences_context: context.signed_key,
|
13
|
+
allow_match_all: allow_match_all,
|
14
|
+
allow_individuals: allow_individuals,
|
12
15
|
},
|
13
16
|
class: html_class)
|
14
17
|
end
|
data/lib/audiences/engine.rb
CHANGED
@@ -10,8 +10,17 @@ module Audiences
|
|
10
10
|
class Engine < ::Rails::Engine
|
11
11
|
isolate_namespace Audiences
|
12
12
|
|
13
|
-
initializer "audiences.
|
13
|
+
initializer "audiences.editor_helper" do |app|
|
14
14
|
app.config.assets.precompile += %w[audiences-ujs.js] if app.config.respond_to?(:assets)
|
15
|
+
|
16
|
+
ActiveSupport.on_load(:action_view) do
|
17
|
+
require "audiences/editor_helper"
|
18
|
+
include Audiences::EditorHelper
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
initializer "audiences.logger" do
|
23
|
+
Audiences.config.logger ||= Rails.logger.tagged("Audiences")
|
15
24
|
end
|
16
25
|
|
17
26
|
initializer "audiences.model" do
|
@@ -21,5 +30,14 @@ module Audiences
|
|
21
30
|
end
|
22
31
|
end
|
23
32
|
end
|
33
|
+
|
34
|
+
initializer "audiences.observers" do
|
35
|
+
if Audiences.config.observe_scim
|
36
|
+
Audiences::Scim::UpsertUsersObserver.start
|
37
|
+
Audiences::Scim::UpsertGroupsObserver.start
|
38
|
+
Audiences::Scim::PatchGroupsObserver.start
|
39
|
+
Audiences::Scim::PatchUsersObserver.start
|
40
|
+
end
|
41
|
+
end
|
24
42
|
end
|
25
43
|
end
|
data/lib/audiences/model.rb
CHANGED
@@ -10,25 +10,26 @@ module Audiences
|
|
10
10
|
#
|
11
11
|
# @param name [Symbol,String] the member relationship name
|
12
12
|
#
|
13
|
-
# rubocop:disable Naming/PredicateName,Metrics/MethodLength
|
13
|
+
# rubocop:disable Naming/PredicateName,Metrics/MethodLength
|
14
14
|
def has_audience(name)
|
15
15
|
has_one :"#{name}_context", -> { where(relation: name) },
|
16
16
|
as: :owner, dependent: :destroy,
|
17
17
|
class_name: "Audiences::Context"
|
18
|
-
has_many :"#{name}_external_users",
|
19
|
-
through: :"#{name}_context", source: :users,
|
20
|
-
class_name: "Audiences::ExternalUser"
|
21
|
-
has_many name, -> { readonly }, through: :"#{name}_external_users", source: :identity
|
22
18
|
|
23
|
-
|
19
|
+
delegate :users, to: :"#{name}_context", prefix: :"#{name}_external"
|
20
|
+
|
21
|
+
define_method(name) do
|
22
|
+
send(:"#{name}_context").users.includes(:identity)
|
23
|
+
.map(&:identity)
|
24
|
+
end
|
25
|
+
|
24
26
|
scope :"with_#{name}_context", -> { includes(:"#{name}_context") }
|
25
|
-
scope :"with_#{name}_external_users", -> { includes(:"#{name}_external_users") }
|
26
27
|
|
27
28
|
after_initialize if: :new_record? do
|
28
29
|
association(:"#{name}_context").build
|
29
30
|
end
|
30
31
|
end
|
31
|
-
# rubocop:enable Naming/PredicateName,Metrics/MethodLength
|
32
|
+
# rubocop:enable Naming/PredicateName,Metrics/MethodLength
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -29,8 +29,10 @@ module Audiences
|
|
29
29
|
#
|
30
30
|
# @param context [Audiences::Context] updated context
|
31
31
|
#
|
32
|
-
def publish(
|
33
|
-
|
32
|
+
def publish(*contexts)
|
33
|
+
contexts.each do |context|
|
34
|
+
subscriptions[context.owner.class]&.call(context)
|
35
|
+
end
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class FieldMapping
|
6
|
+
def initialize(mapping)
|
7
|
+
@map = mapping
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove(object, path, val)
|
11
|
+
current = object.send to(path)
|
12
|
+
_set object, path, current - value(path, val)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(object, path, val)
|
16
|
+
current = object.send to(path)
|
17
|
+
_set object, path, current + value(path, val)
|
18
|
+
end
|
19
|
+
|
20
|
+
def replace(object, path, val)
|
21
|
+
_set object, path, value(path, val)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def _set(object, path, val)
|
27
|
+
return unless @map.key?(path)
|
28
|
+
|
29
|
+
object.send :"#{to(path)}=", val if @map[path]
|
30
|
+
end
|
31
|
+
|
32
|
+
def has?(...) = @map.key?(...)
|
33
|
+
|
34
|
+
def to(path)
|
35
|
+
case @map[path]
|
36
|
+
in { to: to } then to
|
37
|
+
in Symbol then @map[path]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def value(path, val)
|
42
|
+
case @map[path]
|
43
|
+
in { find: find } then [val].flatten.pluck("value").map(&find)
|
44
|
+
else val
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class PatchGroupsObserver < ObserverBase
|
6
|
+
Audiences.config.group_types.each do |group_type|
|
7
|
+
subscribe_to "two_percent.scim.update.#{group_type}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def process
|
11
|
+
Audiences.logger.info "Patching group #{group.display_name} (#{group.scim_id})"
|
12
|
+
|
13
|
+
patch_op.process(group, attributes_mapping)
|
14
|
+
|
15
|
+
group.save!
|
16
|
+
|
17
|
+
propagate_changes_to_users!
|
18
|
+
rescue => e
|
19
|
+
Audiences.logger.error e
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def patch_op = PatchOp.new(event_payload.params)
|
26
|
+
|
27
|
+
def attributes_mapping
|
28
|
+
FieldMapping.new("displayName" => :display_name,
|
29
|
+
"externalId" => :external_id,
|
30
|
+
"urn:ietf:params:scim:schemas:extension:authservice:2.0:Group:active" => :active,
|
31
|
+
"members" => { to: :external_users,
|
32
|
+
find: ->(value) { ExternalUser.find_by(user_id: value) } })
|
33
|
+
end
|
34
|
+
|
35
|
+
def group
|
36
|
+
@group ||= Group.find_by!(resource_type: event_payload.resource,
|
37
|
+
scim_id: event_payload.id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def propagate_changes_to_users!
|
41
|
+
patch_op.operations.each do |operation|
|
42
|
+
next unless operation.path == "members"
|
43
|
+
|
44
|
+
ExternalUser.where(user_id: operation.value.pluck("value")).find_each do |user|
|
45
|
+
TwoPercent::ReplaceEvent.create(resource: "Users", id: user.scim_id, params: user.as_scim)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class PatchOp
|
6
|
+
attr_reader :operations
|
7
|
+
|
8
|
+
def initialize(patch_op)
|
9
|
+
@operations = patch_op["Operations"].flat_map do |operation|
|
10
|
+
derive_operation(operation)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def process(object, operator)
|
15
|
+
@operations.each { _1.process(object, operator) }
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
Operation = Struct.new(:op, :path, :value) do
|
21
|
+
def process(object, operator)
|
22
|
+
raise "Operation #{op} is unknown to #{operator.class}" unless operator.respond_to?(op)
|
23
|
+
|
24
|
+
operator.public_send(op, object, path, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def derive_operation(operation)
|
29
|
+
case operation["value"]
|
30
|
+
when Hash
|
31
|
+
operation["value"].flat_map do |key, value|
|
32
|
+
derive_operation("op" => operation["op"],
|
33
|
+
"path" => [operation["path"], key].compact.join("."),
|
34
|
+
"value" => value)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
[Operation.new(operation["op"], operation["path"], operation["value"])]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class PatchUsersObserver < ObserverBase
|
6
|
+
subscribe_to "two_percent.scim.update.Users"
|
7
|
+
|
8
|
+
def process
|
9
|
+
Audiences.logger.info "Patching user #{user.display_name} (#{user.scim_id})"
|
10
|
+
|
11
|
+
process_attributes!
|
12
|
+
process_data!
|
13
|
+
|
14
|
+
user.save!
|
15
|
+
rescue => e
|
16
|
+
Audiences.logger.error e
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def patch_op = PatchOp.new(event_payload.params)
|
23
|
+
|
24
|
+
def process_data! = patch_op.process(user, ScimData.new)
|
25
|
+
|
26
|
+
def process_attributes!
|
27
|
+
patch_op.process user, FieldMapping.new("externalId" => :user_id,
|
28
|
+
"displayName" => :display_name,
|
29
|
+
"active" => :active,
|
30
|
+
"photos" => { to: :picture_urls, find: :itself })
|
31
|
+
end
|
32
|
+
|
33
|
+
def user
|
34
|
+
@user ||= Audiences::ExternalUser.find_by!(scim_id: event_payload.id)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class ScimData
|
6
|
+
def replace(object, key, value) = _replace(object.data || {}, key.split("."), value)
|
7
|
+
|
8
|
+
def add(object, key, val)
|
9
|
+
value = object.data&.dig(*key.split(".")) || []
|
10
|
+
replace(key, [...value, val])
|
11
|
+
end
|
12
|
+
|
13
|
+
def remove(object, key, val)
|
14
|
+
values = object.data&.dig(*key.split(".")) || []
|
15
|
+
to_remove = [val].flatten.pluck("value")
|
16
|
+
replace(key, values&.reject { |value| to_remove.include?(value["value"]) })
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def _replace(data, key, val)
|
22
|
+
first_key, *rest_keys = key
|
23
|
+
if rest_keys.empty?
|
24
|
+
data[first_key] = val
|
25
|
+
else
|
26
|
+
_replace(data[first_key] ||= {}, rest_keys, val)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class UpsertGroupsObserver < ObserverBase
|
6
|
+
Audiences.config.group_types.each do |group_type|
|
7
|
+
subscribe_to "two_percent.scim.create.#{group_type}"
|
8
|
+
subscribe_to "two_percent.scim.replace.#{group_type}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
Audiences.logger.info "#{upsert_action} group #{new_display_name} (#{new_external_id})"
|
13
|
+
|
14
|
+
group.update! external_id: new_external_id, display_name: new_display_name, active: new_active
|
15
|
+
rescue => e
|
16
|
+
Audiences.logger.error e
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def upsert_action = group.persisted? ? "Updating" : "Creating"
|
23
|
+
|
24
|
+
def new_external_id = event_payload.params["externalId"]
|
25
|
+
|
26
|
+
def new_display_name = event_payload.params["displayName"]
|
27
|
+
|
28
|
+
def new_active
|
29
|
+
active = event_payload.params.dig("urn:ietf:params:scim:schemas:extension:authservice:2.0:Group", "active")
|
30
|
+
active.nil? || active
|
31
|
+
end
|
32
|
+
|
33
|
+
def group
|
34
|
+
@group ||= Audiences::Group.where(resource_type: event_payload.resource,
|
35
|
+
scim_id: event_payload.params["id"])
|
36
|
+
.first_or_initialize
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Audiences
|
4
|
+
module Scim
|
5
|
+
class UpsertUsersObserver < ObserverBase
|
6
|
+
subscribe_to "two_percent.scim.create.Users"
|
7
|
+
subscribe_to "two_percent.scim.replace.Users"
|
8
|
+
|
9
|
+
def process
|
10
|
+
Audiences.logger.info "#{upsert_action} group #{event_payload.params['displayName']} (#{scim_id})"
|
11
|
+
|
12
|
+
external_user.update! updated_attributes
|
13
|
+
rescue => e
|
14
|
+
Audiences.logger.error e
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def scim_id = event_payload.params["id"]
|
21
|
+
|
22
|
+
def external_user
|
23
|
+
@external_user ||= Audiences::ExternalUser.where(scim_id: scim_id).first_or_initialize
|
24
|
+
end
|
25
|
+
|
26
|
+
def upsert_action = external_user.persisted? ? "Updating" : "Creating"
|
27
|
+
|
28
|
+
def updated_attributes
|
29
|
+
{
|
30
|
+
user_id: event_payload.params["externalId"],
|
31
|
+
display_name: event_payload.params["displayName"],
|
32
|
+
picture_urls: new_picture_urls,
|
33
|
+
data: event_payload.params,
|
34
|
+
groups: new_groups,
|
35
|
+
active: event_payload.params.fetch("active", false),
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_picture_urls = event_payload.params["photos"]&.pluck("value")
|
40
|
+
|
41
|
+
def new_groups
|
42
|
+
event_payload.params.fetch("groups", []).filter_map do |group|
|
43
|
+
Audiences::Group.find_by(scim_id: group["value"])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/audiences/scim.rb
CHANGED
@@ -2,20 +2,14 @@
|
|
2
2
|
|
3
3
|
module Audiences
|
4
4
|
module Scim
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def resource(type)
|
16
|
-
Audiences.config.resources.fetch(type) do
|
17
|
-
Resource.new(type: type)
|
18
|
-
end
|
19
|
-
end
|
5
|
+
autoload :ScimData, "audiences/scim/scim_data"
|
6
|
+
autoload :FieldMapping, "audiences/scim/field_mapping"
|
7
|
+
autoload :PatchOp, "audiences/scim/patch_op"
|
8
|
+
|
9
|
+
autoload :ObserverBase, "audiences/scim/observer_base"
|
10
|
+
autoload :PatchGroupsObserver, "audiences/scim/patch_groups_observer"
|
11
|
+
autoload :PatchUsersObserver, "audiences/scim/patch_users_observer"
|
12
|
+
autoload :UpsertGroupsObserver, "audiences/scim/upsert_groups_observer"
|
13
|
+
autoload :UpsertUsersObserver, "audiences/scim/upsert_users_observer"
|
20
14
|
end
|
21
15
|
end
|
data/lib/audiences/version.rb
CHANGED
data/lib/audiences.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "aether_observatory"
|
4
|
+
|
3
5
|
# Audiences system
|
4
6
|
# Audiences pushes notifications to your rails app when a
|
5
7
|
# SCIM backend updates a user, notifying matching audiences.
|
@@ -17,7 +19,7 @@ module_function
|
|
17
19
|
# Params might contain:
|
18
20
|
#
|
19
21
|
# match_all: Boolean
|
20
|
-
# criteria: Array<{ <group_type>: Array<Integer> }>
|
22
|
+
# criteria: { groups: Array<{ <group_type>: Array<{ id: Integer }> }> }
|
21
23
|
#
|
22
24
|
# @param token [String] a signed token (see #sign)
|
23
25
|
# @param params [Hash] the updated params
|
@@ -27,13 +29,11 @@ module_function
|
|
27
29
|
Audiences::Context.load(key) do |context|
|
28
30
|
context.update!(
|
29
31
|
match_all: match_all,
|
30
|
-
|
31
|
-
|
32
|
+
extra_users: ::Audiences::ExternalUser.from_scim(*extra_users.map(&:with_indifferent_access)),
|
33
|
+
criteria: ::Audiences::Criterion.map(criteria.map(&:with_indifferent_access))
|
32
34
|
)
|
33
|
-
context.refresh_users!
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
39
|
require "audiences/configuration"
|
39
|
-
require "audiences/railtie"
|