scimitar 1.5.2 → 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/controllers/scimitar/active_record_backed_resources_controller.rb +6 -27
- data/app/controllers/scimitar/application_controller.rb +9 -29
- data/app/models/scimitar/engine_configuration.rb +3 -7
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/errors.rb +1 -1
- data/app/models/scimitar/lists/query_parser.rb +4 -14
- data/app/models/scimitar/resources/base.rb +1 -1
- data/app/models/scimitar/resources/mixin.rb +4 -113
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +1 -1
- data/app/models/scimitar/schema/base.rb +3 -1
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/config/initializers/scimitar.rb +70 -86
- data/lib/scimitar/version.rb +2 -2
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
- data/spec/apps/dummy/app/models/mock_group.rb +1 -1
- data/spec/apps/dummy/app/models/mock_user.rb +8 -19
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +9 -44
- data/spec/apps/dummy/config/routes.rb +0 -4
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -9
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
- data/spec/apps/dummy/db/schema.rb +4 -10
- data/spec/controllers/scimitar/application_controller_spec.rb +1 -70
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -9
- data/spec/models/scimitar/resources/base_spec.rb +66 -161
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +43 -757
- data/spec/models/scimitar/resources/user_spec.rb +4 -4
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -0
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +0 -10
- data/spec/requests/active_record_backed_resources_controller_spec.rb +40 -309
- data/spec/requests/application_controller_spec.rb +3 -17
- metadata +7 -7
data/lib/scimitar/version.rb
CHANGED
@@ -3,11 +3,11 @@ module Scimitar
|
|
3
3
|
# Gem version. If this changes, be sure to re-run "bundle install" or
|
4
4
|
# "bundle update".
|
5
5
|
#
|
6
|
-
VERSION = '
|
6
|
+
VERSION = '2.0.0'
|
7
7
|
|
8
8
|
# Date for VERSION. If this changes, be sure to re-run "bundle install"
|
9
9
|
# or "bundle update".
|
10
10
|
#
|
11
|
-
DATE = '
|
11
|
+
DATE = '2022-03-04'
|
12
12
|
|
13
13
|
end
|
@@ -5,7 +5,7 @@ class MockUser < ActiveRecord::Base
|
|
5
5
|
# ===========================================================================
|
6
6
|
|
7
7
|
READWRITE_ATTRS = %w{
|
8
|
-
|
8
|
+
id
|
9
9
|
scim_uid
|
10
10
|
username
|
11
11
|
first_name
|
@@ -13,8 +13,6 @@ class MockUser < ActiveRecord::Base
|
|
13
13
|
work_email_address
|
14
14
|
home_email_address
|
15
15
|
work_phone_number
|
16
|
-
organization
|
17
|
-
department
|
18
16
|
}
|
19
17
|
|
20
18
|
has_and_belongs_to_many :mock_groups
|
@@ -40,7 +38,7 @@ class MockUser < ActiveRecord::Base
|
|
40
38
|
|
41
39
|
def self.scim_attributes_map
|
42
40
|
return {
|
43
|
-
id: :
|
41
|
+
id: :id,
|
44
42
|
externalId: :scim_uid,
|
45
43
|
userName: :username,
|
46
44
|
name: {
|
@@ -84,13 +82,7 @@ class MockUser < ActiveRecord::Base
|
|
84
82
|
}
|
85
83
|
}
|
86
84
|
],
|
87
|
-
active: :is_active
|
88
|
-
|
89
|
-
# Custom extension schema - see configuration in
|
90
|
-
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
91
|
-
#
|
92
|
-
organization: :organization,
|
93
|
-
department: :department
|
85
|
+
active: :is_active
|
94
86
|
}
|
95
87
|
end
|
96
88
|
|
@@ -100,14 +92,11 @@ class MockUser < ActiveRecord::Base
|
|
100
92
|
|
101
93
|
def self.scim_queryable_attributes
|
102
94
|
return {
|
103
|
-
'
|
104
|
-
'
|
105
|
-
'
|
106
|
-
'
|
107
|
-
'
|
108
|
-
'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
109
|
-
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
110
|
-
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
|
95
|
+
'name.givenName' => { column: :first_name },
|
96
|
+
'name.familyName' => { column: :last_name },
|
97
|
+
'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
98
|
+
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
99
|
+
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
|
111
100
|
}
|
112
101
|
end
|
113
102
|
|
@@ -1,15 +1,38 @@
|
|
1
|
+
require 'active_support/core_ext/integer/time'
|
2
|
+
|
1
3
|
Rails.application.configure do
|
2
4
|
config.cache_classes = true
|
3
5
|
config.eager_load = false
|
4
|
-
config.serve_static_files = true
|
5
|
-
config.static_cache_control = 'public, max-age=3600'
|
6
|
-
config.consider_all_requests_local = true
|
7
6
|
|
8
|
-
|
7
|
+
# Configure public file server for tests with Cache-Control for performance.
|
8
|
+
config.public_file_server.enabled = true
|
9
|
+
config.public_file_server.headers = {
|
10
|
+
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
|
11
|
+
}
|
9
12
|
|
13
|
+
# Show full error reports and disable caching.
|
14
|
+
config.consider_all_requests_local = true
|
10
15
|
config.action_controller.perform_caching = false
|
16
|
+
config.cache_store = :null_store
|
17
|
+
|
18
|
+
# Raise exceptions instead of rendering exception templates.
|
19
|
+
config.action_dispatch.show_exceptions = false
|
20
|
+
|
21
|
+
# Disable request forgery protection in test environment.
|
11
22
|
config.action_controller.allow_forgery_protection = false
|
12
23
|
|
13
|
-
|
24
|
+
# Print deprecation notices to the stderr.
|
14
25
|
config.active_support.deprecation = :stderr
|
26
|
+
|
27
|
+
# Raise exceptions for disallowed deprecations.
|
28
|
+
config.active_support.disallowed_deprecation = :raise
|
29
|
+
|
30
|
+
# Tell Active Support which deprecation messages to disallow.
|
31
|
+
config.active_support.disallowed_deprecation_warnings = []
|
32
|
+
|
33
|
+
# Raises error for missing translations.
|
34
|
+
config.i18n.raise_on_missing_translations = true
|
35
|
+
|
36
|
+
# Annotate rendered view with file names.
|
37
|
+
# config.action_view.annotate_rendered_view_with_filenames = true
|
15
38
|
end
|
@@ -1,51 +1,16 @@
|
|
1
1
|
# Test app configuration.
|
2
2
|
#
|
3
|
-
|
4
|
-
|
5
|
-
# some of the code from which Scimitar was originally built is that those
|
6
|
-
# extensions are done with class-level ivars, so it is largely impossible (or
|
7
|
-
# at least, impractical in tests) to avoid polluting the core class itself
|
8
|
-
# with the extension.
|
9
|
-
#
|
10
|
-
# All related schema tests are written with this in mind.
|
11
|
-
#
|
12
|
-
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
3
|
+
Rails.application.config.to_prepare do
|
4
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
13
5
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
application_controller_mixin: Module.new do
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
def test_hook; end
|
10
|
+
before_action :test_hook
|
11
|
+
end
|
19
12
|
end
|
20
13
|
end
|
21
|
-
end
|
22
|
-
|
23
|
-
})
|
24
|
-
|
25
|
-
module ScimSchemaExtensions
|
26
|
-
module User
|
27
|
-
class Enterprise < Scimitar::Schema::Base
|
28
|
-
def initialize(options = {})
|
29
|
-
super(
|
30
|
-
name: 'ExtendedUser',
|
31
|
-
description: 'Enterprise extension for a User',
|
32
|
-
id: self.class.id,
|
33
|
-
scim_attributes: self.class.scim_attributes
|
34
|
-
)
|
35
|
-
end
|
36
14
|
|
37
|
-
|
38
|
-
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.scim_attributes
|
42
|
-
[
|
43
|
-
Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
|
44
|
-
Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
|
45
|
-
]
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
15
|
+
})
|
49
16
|
end
|
50
|
-
|
51
|
-
Scimitar::Resources::User.extend_schema ScimSchemaExtensions::User::Enterprise
|
@@ -13,10 +13,6 @@ Rails.application.routes.draw do
|
|
13
13
|
patch 'Users/:id', to: 'mock_users#update'
|
14
14
|
delete 'Users/:id', to: 'mock_users#destroy'
|
15
15
|
|
16
|
-
get 'Groups', to: 'mock_groups#index'
|
17
|
-
get 'Groups/:id', to: 'mock_groups#show'
|
18
|
-
patch 'Groups/:id', to: 'mock_groups#update'
|
19
|
-
|
20
16
|
# For testing blocks passed to ActiveRecordBackedResourcesController#destroy
|
21
17
|
#
|
22
18
|
delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
|
@@ -1,10 +1,7 @@
|
|
1
1
|
class CreateMockUsers < ActiveRecord::Migration[6.1]
|
2
2
|
def change
|
3
|
-
create_table :mock_users
|
4
|
-
t.timestamps
|
3
|
+
create_table :mock_users do |t|
|
5
4
|
|
6
|
-
# Support part of the core schema
|
7
|
-
#
|
8
5
|
t.text :scim_uid
|
9
6
|
t.text :username
|
10
7
|
t.text :first_name
|
@@ -13,11 +10,6 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
13
10
|
t.text :home_email_address
|
14
11
|
t.text :work_phone_number
|
15
12
|
|
16
|
-
# Support the custom extension schema - see configuration in
|
17
|
-
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
18
|
-
#
|
19
|
-
t.text :organization
|
20
|
-
t.text :department
|
21
13
|
end
|
22
14
|
end
|
23
15
|
end
|
@@ -1,13 +1,8 @@
|
|
1
1
|
class CreateJoinTableMockGroupsMockUsers < ActiveRecord::Migration[6.1]
|
2
2
|
def change
|
3
|
-
|
4
|
-
t.
|
5
|
-
t.
|
6
|
-
|
7
|
-
# The 'foreign_key:' option (used above) only works for 'id' column names
|
8
|
-
# but the test data has a column named 'primary_key' for 'mock_users'.
|
9
|
-
#
|
10
|
-
t.foreign_key :mock_users, primary_key: :primary_key
|
3
|
+
create_join_table :mock_groups, :mock_users do |t|
|
4
|
+
t.index [:mock_group_id, :mock_user_id]
|
5
|
+
t.index [:mock_user_id, :mock_group_id]
|
11
6
|
end
|
12
7
|
end
|
13
8
|
end
|
@@ -24,14 +24,12 @@ ActiveRecord::Schema.define(version: 2021_03_08_044214) do
|
|
24
24
|
|
25
25
|
create_table "mock_groups_users", id: false, force: :cascade do |t|
|
26
26
|
t.bigint "mock_group_id", null: false
|
27
|
-
t.
|
28
|
-
t.index ["mock_group_id"], name: "
|
29
|
-
t.index ["mock_user_id"], name: "
|
27
|
+
t.bigint "mock_user_id", null: false
|
28
|
+
t.index ["mock_group_id", "mock_user_id"], name: "index_mock_groups_users_on_mock_group_id_and_mock_user_id"
|
29
|
+
t.index ["mock_user_id", "mock_group_id"], name: "index_mock_groups_users_on_mock_user_id_and_mock_group_id"
|
30
30
|
end
|
31
31
|
|
32
|
-
create_table "mock_users",
|
33
|
-
t.datetime "created_at", precision: 6, null: false
|
34
|
-
t.datetime "updated_at", precision: 6, null: false
|
32
|
+
create_table "mock_users", force: :cascade do |t|
|
35
33
|
t.text "scim_uid"
|
36
34
|
t.text "username"
|
37
35
|
t.text "first_name"
|
@@ -39,10 +37,6 @@ ActiveRecord::Schema.define(version: 2021_03_08_044214) do
|
|
39
37
|
t.text "work_email_address"
|
40
38
|
t.text "home_email_address"
|
41
39
|
t.text "work_phone_number"
|
42
|
-
t.text "organization"
|
43
|
-
t.text "department"
|
44
40
|
end
|
45
41
|
|
46
|
-
add_foreign_key "mock_groups_users", "mock_groups"
|
47
|
-
add_foreign_key "mock_groups_users", "mock_users", primary_key: "primary_key"
|
48
42
|
end
|
@@ -169,74 +169,5 @@ RSpec.describe Scimitar::ApplicationController do
|
|
169
169
|
expect(parsed_body).to include('status' => '500')
|
170
170
|
expect(parsed_body).to include('detail' => 'Bang')
|
171
171
|
end
|
172
|
-
|
173
|
-
context 'with an exception reporter' do
|
174
|
-
around :each do | example |
|
175
|
-
original_configuration = Scimitar.engine_configuration.exception_reporter
|
176
|
-
Scimitar.engine_configuration.exception_reporter = Proc.new do | exception |
|
177
|
-
@exception = exception
|
178
|
-
end
|
179
|
-
example.run()
|
180
|
-
ensure
|
181
|
-
Scimitar.engine_configuration.exception_reporter = original_configuration
|
182
|
-
end
|
183
|
-
|
184
|
-
context 'and "internal server error"' do
|
185
|
-
it 'is invoked' do
|
186
|
-
get :index, params: { format: :scim }
|
187
|
-
|
188
|
-
expect(@exception).to be_a(RuntimeError)
|
189
|
-
expect(@exception.message).to eql('Bang')
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
context 'and "not found"' do
|
194
|
-
controller do
|
195
|
-
def index
|
196
|
-
handle_resource_not_found(ActiveRecord::RecordNotFound.new(42))
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
it 'is invoked' do
|
201
|
-
get :index, params: { format: :scim }
|
202
|
-
|
203
|
-
expect(@exception).to be_a(ActiveRecord::RecordNotFound)
|
204
|
-
expect(@exception.message).to eql('42')
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
context 'and bad JSON' do
|
209
|
-
controller do
|
210
|
-
def index
|
211
|
-
begin
|
212
|
-
raise 'Hello'
|
213
|
-
rescue
|
214
|
-
raise ActionDispatch::Http::Parameters::ParseError
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
it 'is invoked' do
|
220
|
-
get :index, params: { format: :scim }
|
221
|
-
|
222
|
-
expect(@exception).to be_a(ActionDispatch::Http::Parameters::ParseError)
|
223
|
-
expect(@exception.message).to eql('Hello')
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
context 'and a bad content type' do
|
228
|
-
controller do
|
229
|
-
def index; end
|
230
|
-
end
|
231
|
-
|
232
|
-
it 'is invoked' do
|
233
|
-
request.headers['Content-Type'] = 'text/plain'
|
234
|
-
get :index
|
235
|
-
|
236
|
-
expect(@exception).to be_a(Scimitar::ErrorResponse)
|
237
|
-
expect(@exception.message).to eql('Only application/scim+json type is accepted.')
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end # "context 'exception reporter' do"
|
241
|
-
end # "context 'error handling' do"
|
172
|
+
end
|
242
173
|
end
|
@@ -14,9 +14,9 @@ RSpec.describe Scimitar::SchemasController do
|
|
14
14
|
get :index, params: { format: :scim }
|
15
15
|
expect(response).to be_ok
|
16
16
|
parsed_body = JSON.parse(response.body)
|
17
|
-
expect(parsed_body.length).to eql(
|
17
|
+
expect(parsed_body.length).to eql(2)
|
18
18
|
schema_names = parsed_body.map {|schema| schema['name']}
|
19
|
-
expect(schema_names).to match_array(['User', '
|
19
|
+
expect(schema_names).to match_array(['User', 'Group'])
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'returns only the User schema when its id is provided' do
|
@@ -405,19 +405,19 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
405
405
|
query = @instance.to_activerecord_query(MockUser.all)
|
406
406
|
|
407
407
|
expect(query.count).to eql(1)
|
408
|
-
expect(query.pluck(:
|
408
|
+
expect(query.pluck(:id)).to eql([user_1.id])
|
409
409
|
|
410
410
|
@instance.parse('name.givenName sw J') # First name starts with 'J'
|
411
411
|
query = @instance.to_activerecord_query(MockUser.all)
|
412
412
|
|
413
413
|
expect(query.count).to eql(2)
|
414
|
-
expect(query.pluck(:
|
414
|
+
expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
|
415
415
|
|
416
416
|
@instance.parse('name.familyName ew he') # Last name ends with 'he'
|
417
417
|
query = @instance.to_activerecord_query(MockUser.all)
|
418
418
|
|
419
419
|
expect(query.count).to eql(1)
|
420
|
-
expect(query.pluck(:
|
420
|
+
expect(query.pluck(:id)).to eql([user_2.id])
|
421
421
|
|
422
422
|
# Test presence
|
423
423
|
|
@@ -425,7 +425,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
425
425
|
query = @instance.to_activerecord_query(MockUser.all)
|
426
426
|
|
427
427
|
expect(query.count).to eql(2)
|
428
|
-
expect(query.pluck(:
|
428
|
+
expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
|
429
429
|
|
430
430
|
# Test a simple not-equals, but use a custom starting scope. Note that
|
431
431
|
# the query would find "user_3" *except* there is no first name defined
|
@@ -435,7 +435,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
435
435
|
query = @instance.to_activerecord_query(MockUser.where.not('first_name' => 'John'))
|
436
436
|
|
437
437
|
expect(query.count).to eql(1)
|
438
|
-
expect(query.pluck(:
|
438
|
+
expect(query.pluck(:id)).to match_array([user_1.id])
|
439
439
|
end
|
440
440
|
|
441
441
|
context 'when mapped to multiple columns' do
|
@@ -499,7 +499,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
499
499
|
query = @instance.to_activerecord_query(MockUser.all)
|
500
500
|
|
501
501
|
expect(query.count).to eql(1)
|
502
|
-
expect(query.pluck(:
|
502
|
+
expect(query.pluck(:id)).to match_array([user_2.id])
|
503
503
|
end
|
504
504
|
end # "context 'simple AND' do"
|
505
505
|
|
@@ -520,7 +520,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
520
520
|
query = @instance.to_activerecord_query(MockUser.all)
|
521
521
|
|
522
522
|
expect(query.count).to eql(2)
|
523
|
-
expect(query.pluck(:
|
523
|
+
expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
|
524
524
|
end
|
525
525
|
end # "context 'simple OR' do"
|
526
526
|
|
@@ -546,7 +546,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
546
546
|
query = @instance.to_activerecord_query(MockUser.all)
|
547
547
|
|
548
548
|
expect(query.count).to eql(3)
|
549
|
-
expect(query.pluck(:
|
549
|
+
expect(query.pluck(:id)).to match_array([user_1.id, user_2.id, user_3.id])
|
550
550
|
end
|
551
551
|
end # "context 'combined AND and OR' do"
|
552
552
|
|
@@ -590,7 +590,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
|
|
590
590
|
end
|
591
591
|
|
592
592
|
it 'complains if there is no column mapping available' do
|
593
|
-
expect { @instance.send(:activerecord_columns, '
|
593
|
+
expect { @instance.send(:activerecord_columns, 'externalId') }.to raise_error(Scimitar::FilterError)
|
594
594
|
end
|
595
595
|
|
596
596
|
it 'complains about malformed declarations' do
|