audiences 3.1.0 → 3.2.1
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/controllers/audiences/scim_proxy_controller.rb +10 -16
- data/app/models/audiences/users_search.rb +6 -8
- data/docs/CHANGELOG.md +22 -1
- data/docs/README.md +95 -14
- data/lib/audiences/configuration.rb +18 -0
- data/lib/audiences/read_sources/legacy.rb +30 -0
- data/lib/audiences/version.rb +1 -1
- data/lib/audiences.rb +4 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca6dae8a08e2e2703e139555772d0a1143cb8511b0cd4c5820d6f3f2c3c91581
|
|
4
|
+
data.tar.gz: 6327e89cd3a1970bb8cffb02ebaef89b5e71d48dfb8d6321758aa3159484cec1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 390dff4c5768d1a7e7d6745e5f4af302cdd6c6c9f55bb675edd246408bd59daa4221ded9878360fc03ecf078f20a5fba9ae23d5401c5f3fc956863e4950e2679
|
|
7
|
+
data.tar.gz: b669cd8ff6d4ccc7a1b3c70b4c761b888b84c13ee6f4ff7c89498cf864e8d010a662992049f56173b4eca844777402728167c78c67ef58cf23b9fae28dc7459c
|
|
@@ -2,23 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Audiences
|
|
4
4
|
class ScimProxyController < ApplicationController
|
|
5
|
+
# Reads resources through the configured read source. The source is
|
|
6
|
+
# swappable via Audiences.config.read_source (defaults to the legacy
|
|
7
|
+
# source that reads Audiences' own projection), so the controller stays
|
|
8
|
+
# agnostic to where the data comes from.
|
|
5
9
|
def get
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def scope
|
|
16
|
-
if params[:scim_path].eql?("Users")
|
|
17
|
-
Audiences::ExternalUser.instance_exec(&Audiences.default_users_scope)
|
|
18
|
-
else
|
|
19
|
-
Audiences::Group.where(resource_type: params[:scim_path])
|
|
20
|
-
.instance_exec(&Audiences.default_groups_scope)
|
|
21
|
-
end
|
|
10
|
+
render json: Audiences.read_source.fetch(
|
|
11
|
+
resource_type: params[:scim_path],
|
|
12
|
+
query: params[:query],
|
|
13
|
+
start_index: params[:startIndex],
|
|
14
|
+
count: params[:count]
|
|
15
|
+
)
|
|
22
16
|
end
|
|
23
17
|
end
|
|
24
18
|
end
|
|
@@ -27,14 +27,12 @@ module Audiences
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def result
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
else "CAST(data AS CHAR)"
|
|
37
|
-
end
|
|
30
|
+
sql = if @scope.connection.adapter_name == "PostgreSQL"
|
|
31
|
+
"CAST(data AS TEXT) LIKE ?"
|
|
32
|
+
else
|
|
33
|
+
"CAST(data AS CHAR) LIKE ?"
|
|
34
|
+
end
|
|
35
|
+
@result ||= @scope.where(sql, "%#{@query}%")
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
end
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Version 3.2.1 (2026-10-22)
|
|
2
|
+
|
|
3
|
+
**Compatibility:**
|
|
4
|
+
- Restore Rails 7.1 support so applications can adopt the pluggable read source while transitioning to Rails 7.2. Audiences development remains on Rails 8.1, and Rails 7.1 is covered by the compatibility test matrix.
|
|
5
|
+
|
|
6
|
+
# Version 3.2.0 (2026-10-21)
|
|
7
|
+
|
|
8
|
+
**Breaking Changes:**
|
|
9
|
+
- Require Rails 7.2 or newer and Ruby 3.1 or newer. Rails 6.1, 7.0, and 7.1 are no longer supported. [#567](https://github.com/powerhome/audiences/pull/567)
|
|
10
|
+
|
|
11
|
+
**Improvements:**
|
|
12
|
+
- Add `Audiences.config.read_source` and the `Audiences.read_source` accessor so applications can supply a custom source for `GET /scim(/*scim_path)` through a `fetch(resource_type:, query:, start_index:, count:)` interface. [#565](https://github.com/powerhome/audiences/pull/565)
|
|
13
|
+
- Default to `Audiences::ReadSources::Legacy`, preserving local user/group reads, configured scopes, search, pagination, and JSON serialization. Context endpoints and audience calculations continue to use the local projection.
|
|
14
|
+
- Document read-source configuration, reload-safe initialization, the request/response contract, and scope requirements.
|
|
15
|
+
|
|
16
|
+
**Development:**
|
|
17
|
+
- Update the development bundle to Rails 8.1 and cover Rails 7.2, 8.0, and 8.1 with both MySQL and PostgreSQL in CI. Refresh development and appraisal dependencies. [#567](https://github.com/powerhome/audiences/pull/567)
|
|
18
|
+
|
|
19
|
+
# Version 3.1.0 (2026-09-23)
|
|
20
|
+
|
|
21
|
+
**Development:**
|
|
22
|
+
- Update the dummy application's Playbook UI dependency from a prerelease to the released 16.11.0 version. [#562](https://github.com/powerhome/audiences/pull/562)
|
|
2
23
|
|
|
3
24
|
# Version 3.0.0 (2026-06-18)
|
|
4
25
|
|
data/docs/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
Requires Ruby 3.1 or newer and Rails 7.2 or newer. Rails versions may impose a higher Ruby requirement; the Rails 8.1 development bundle requires Ruby 3.2 or newer.
|
|
8
|
+
|
|
7
9
|
Add this line to your application's Gemfile:
|
|
8
10
|
|
|
9
11
|
```ruby
|
|
@@ -42,7 +44,97 @@ For more details, refer to [editor_helper](../lib/audiences/editor_helper.rb).
|
|
|
42
44
|
|
|
43
45
|
### Configuring Audiences
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
#### Read Source
|
|
48
|
+
|
|
49
|
+
The read source backs the resource endpoint, `GET /scim(/*scim_path)`. `ScimProxyController#get` renders whatever the configured source returns:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
render json: Audiences.read_source.fetch(
|
|
53
|
+
resource_type: params[:scim_path],
|
|
54
|
+
query: params[:query],
|
|
55
|
+
start_index: params[:startIndex],
|
|
56
|
+
count: params[:count]
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`Audiences.config.read_source` defaults to `Audiences::ReadSources::Legacy`, which reads Audiences' own `ExternalUser` and `Group` projection. Replacing it with any object that satisfies the `#fetch` contract below lets reads come from another store without Audiences depending on that store.
|
|
61
|
+
|
|
62
|
+
That covers only this endpoint. The context endpoints (`GET /:key` and `GET /:key/users`) and audience calculation keep reading the local projection no matter what the read source does.
|
|
63
|
+
|
|
64
|
+
Assign a replacement from an initializer:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
Audiences.configure do |config|
|
|
68
|
+
config.read_source = MyReadSource.new
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
When the implementation is an autoloaded constant, assign it inside a `to_prepare` block so each reload picks up the new class. Keep the block inside `configure` so `config` stays in scope:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
Audiences.configure do |config|
|
|
76
|
+
Rails.application.config.to_prepare do
|
|
77
|
+
config.read_source = MyReadSource.new
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
##### The `#fetch` contract
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
def fetch(resource_type:, query:, start_index:, count:)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
All four arguments come straight from request params with no casting or defaulting, so each one is a `String` or `nil`.
|
|
89
|
+
|
|
90
|
+
- `resource_type` is the wildcard path segment: `"Users"`, `"Groups"`, `"Departments"`, and so on. The segment is optional in the route, so `GET /scim` passes `nil`.
|
|
91
|
+
- `query` is a substring to match against the display name. `nil` applies no filter.
|
|
92
|
+
- `start_index` is an offset and `count` is a limit, both as strings such as `"2"`. `nil` means no restriction, so omitting `count` returns the whole catalogue. A source doing arithmetic on either has to call `to_i` first.
|
|
93
|
+
|
|
94
|
+
##### The return contract
|
|
95
|
+
|
|
96
|
+
Return a collection `render json:` can serialize: an `ActiveRecord::Relation`, serialized through each record's `as_json`, or an array of hashes rendered as given.
|
|
97
|
+
|
|
98
|
+
Every resource type except `"Users"` returns elements of this shape, where `id` is the SCIM id rather than the primary key:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "id": "<scim_id>", "externalId": "<external_id>", "displayName": "<display_name>" }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A `"Users"` element is the stored SCIM data plus these keys, derived from the user's group memberships:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"groups": [{ "value": "<group scim_id>", "display": "<group display_name>" }],
|
|
109
|
+
"title": "<Titles group display name>",
|
|
110
|
+
"urn:ietf:params:scim:schemas:extension:authservice:2.0:User": {
|
|
111
|
+
"role": "<Roles group display name>",
|
|
112
|
+
"department": "<Departments group display name>",
|
|
113
|
+
"territory": "<Territories group display name>",
|
|
114
|
+
"territoryAbbr": "<territory_abbreviations lookup>"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
That payload is then filtered to the keys in `exposed_user_attributes`, which holds string keys and defaults to `id`, `externalId`, `displayName`, and `photos`. The filter drops any configured key the payload lacks, so listing `title`, `groups`, or the extension URN only works if the source emits them. Legacy builds all of this in `ExternalUser#as_scim`.
|
|
120
|
+
|
|
121
|
+
##### Scopes
|
|
122
|
+
|
|
123
|
+
Legacy applies the configured scopes inside `#fetch`. The controller does not:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
if resource_type == "Users"
|
|
127
|
+
ExternalUser.instance_exec(&Audiences.default_users_scope)
|
|
128
|
+
else
|
|
129
|
+
Group.where(resource_type: resource_type).instance_exec(&Audiences.default_groups_scope)
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Both default to `-> { active }`. A replacement source has to apply them itself; skipping them serves inactive records and bypasses whatever membership restriction the application configured.
|
|
134
|
+
|
|
135
|
+
Each proc is `instance_exec`'d against whatever relation the calling code holds, and there is one `default_users_scope` for the whole gem — the context endpoints always run it against `ExternalUser`. A proc may therefore only call scopes that exist on every model it reaches, and a source backed by another model needs equivalent scopes defined there.
|
|
136
|
+
|
|
137
|
+
#### Adding Audiences to a Model
|
|
46
138
|
|
|
47
139
|
A model object can contain multiple audience contexts using the `has_audience` module helper, which is added to ActiveRecord automatically when configured:
|
|
48
140
|
|
|
@@ -64,7 +156,7 @@ class Survey < ApplicationRecord
|
|
|
64
156
|
end
|
|
65
157
|
```
|
|
66
158
|
|
|
67
|
-
|
|
159
|
+
#### Listening to Audience Changes
|
|
68
160
|
|
|
69
161
|
Audiences allows your app to keep up with mutable groups of people. To react to audience changes, subscribe to audiences related to a certain owner type and handle changes through a block:
|
|
70
162
|
|
|
@@ -97,21 +189,10 @@ See a working example in our dummy app:
|
|
|
97
189
|
- [Job class](../spec/dummy/app/jobs/update_memberships_job.rb)
|
|
98
190
|
- [Example owning model](../spec/dummy/app/models/example_owner.rb)
|
|
99
191
|
|
|
100
|
-
### SCIM Resource Attributes
|
|
101
|
-
|
|
102
|
-
Configure which attributes are requested from the SCIM backend for each resource type. `Audiences` includes `id`, `externalId`, and `displayName` by default in every resource type. It also requests `photos.type` and `photos.value` for users by default. To request additional attributes:
|
|
103
|
-
|
|
104
|
-
```ruby
|
|
105
|
-
Audiences.configure do |config|
|
|
106
|
-
config.resource :Users, attributes: ["name" => %w[givenName familyName formatted]]
|
|
107
|
-
config.resource :Groups, attributes: %w[mfaRequired]
|
|
108
|
-
end
|
|
109
|
-
```
|
|
110
|
-
|
|
111
192
|
## Contributing
|
|
112
193
|
|
|
113
194
|
For more information, see the [development guide](../../docs/development.md).
|
|
114
195
|
|
|
115
196
|
## License
|
|
116
197
|
|
|
117
|
-
This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
198
|
+
This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -79,6 +79,24 @@ module Audiences
|
|
|
79
79
|
->(*) { active }
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# The source that backs the read endpoint (ScimProxyController#get).
|
|
83
|
+
#
|
|
84
|
+
# Defaults to Audiences::ReadSources::Legacy, which reads Audiences' own
|
|
85
|
+
# projection (ExternalUser / Group). Applications can inject an alternative
|
|
86
|
+
# source (e.g. one backed by an external store) as long as it responds to
|
|
87
|
+
# `#fetch(resource_type:, query:, start_index:, count:)` and returns records
|
|
88
|
+
# renderable as the client-facing contract. The controller never references
|
|
89
|
+
# the source's backing models directly, so the read source is swappable
|
|
90
|
+
# without coupling Audiences to any particular provider.
|
|
91
|
+
#
|
|
92
|
+
# I.e.:
|
|
93
|
+
#
|
|
94
|
+
# Audiences.configure do |config|
|
|
95
|
+
# config.read_source = MyExternalReadSource.new
|
|
96
|
+
# end
|
|
97
|
+
#
|
|
98
|
+
config_accessor(:read_source) { Audiences::ReadSources::Legacy.new }
|
|
99
|
+
|
|
82
100
|
# These are the user attributes that will be exposed in the audiences endpoints.
|
|
83
101
|
# They're required by the UI to display the user information.
|
|
84
102
|
#
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Audiences
|
|
4
|
+
module ReadSources
|
|
5
|
+
# Default read source.
|
|
6
|
+
#
|
|
7
|
+
# Reads Audiences' own projection (ExternalUser / Group), preserving the exact
|
|
8
|
+
# scoping, search and pagination behavior the read endpoint has always had.
|
|
9
|
+
# Records are returned as-is so the controller's `render json:` uses each
|
|
10
|
+
# model's `as_json` to produce the client-facing contract.
|
|
11
|
+
class Legacy
|
|
12
|
+
def fetch(resource_type:, query:, start_index:, count:)
|
|
13
|
+
scope(resource_type)
|
|
14
|
+
.search(query)
|
|
15
|
+
.offset(start_index)
|
|
16
|
+
.limit(count)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def scope(resource_type)
|
|
22
|
+
if resource_type == "Users"
|
|
23
|
+
ExternalUser.instance_exec(&Audiences.default_users_scope)
|
|
24
|
+
else
|
|
25
|
+
Group.where(resource_type: resource_type).instance_exec(&Audiences.default_groups_scope)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/audiences/version.rb
CHANGED
data/lib/audiences.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: audiences
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.1
|
|
4
|
+
version: 3.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carlos Palhares
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: aether_observatory
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '7.1'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '7.1'
|
|
41
41
|
description: Audiences notify the Rails app when a SCIM backend updates a user affecting
|
|
42
42
|
matching audiences
|
|
43
43
|
email:
|
|
@@ -100,6 +100,7 @@ files:
|
|
|
100
100
|
- lib/audiences/integrations/upsert_users_observer.rb
|
|
101
101
|
- lib/audiences/model.rb
|
|
102
102
|
- lib/audiences/notifications.rb
|
|
103
|
+
- lib/audiences/read_sources/legacy.rb
|
|
103
104
|
- lib/audiences/version.rb
|
|
104
105
|
- lib/tasks/audiences_tasks.rake
|
|
105
106
|
homepage: https://github.com/powerhome/audiences
|
|
@@ -108,7 +109,7 @@ licenses:
|
|
|
108
109
|
metadata:
|
|
109
110
|
homepage_uri: https://github.com/powerhome/audiences
|
|
110
111
|
source_code_uri: https://github.com/powerhome/audiences
|
|
111
|
-
changelog_uri: https://github.com/powerhome/audiences/blob/main/
|
|
112
|
+
changelog_uri: https://github.com/powerhome/audiences/blob/main/audiences/docs/CHANGELOG.md
|
|
112
113
|
rubygems_mfa_required: 'true'
|
|
113
114
|
post_install_message:
|
|
114
115
|
rdoc_options: []
|
|
@@ -118,7 +119,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
118
119
|
requirements:
|
|
119
120
|
- - ">="
|
|
120
121
|
- !ruby/object:Gem::Version
|
|
121
|
-
version: '
|
|
122
|
+
version: '3.1'
|
|
122
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
124
|
requirements:
|
|
124
125
|
- - ">="
|