scimitar 2.5.0 → 2.11.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/LICENSE.txt +21 -0
- data/README.md +721 -0
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +72 -18
- data/app/controllers/scimitar/application_controller.rb +17 -9
- data/app/controllers/scimitar/resource_types_controller.rb +7 -3
- data/app/controllers/scimitar/resources_controller.rb +0 -2
- data/app/controllers/scimitar/schemas_controller.rb +366 -3
- data/app/controllers/scimitar/service_provider_configurations_controller.rb +3 -2
- data/app/models/scimitar/complex_types/address.rb +0 -6
- data/app/models/scimitar/complex_types/base.rb +2 -2
- data/app/models/scimitar/engine_configuration.rb +3 -1
- data/app/models/scimitar/lists/query_parser.rb +97 -12
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resource_type.rb +4 -6
- data/app/models/scimitar/resources/base.rb +52 -8
- data/app/models/scimitar/resources/mixin.rb +539 -76
- data/app/models/scimitar/schema/attribute.rb +18 -8
- data/app/models/scimitar/schema/base.rb +2 -2
- data/app/models/scimitar/schema/name.rb +2 -2
- data/app/models/scimitar/schema/user.rb +10 -10
- data/config/initializers/scimitar.rb +49 -3
- data/lib/scimitar/engine.rb +57 -12
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +140 -10
- data/lib/scimitar/support/utilities.rb +111 -0
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +1 -0
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
- data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/models/mock_user.rb +20 -3
- data/spec/apps/dummy/config/application.rb +8 -0
- data/spec/apps/dummy/config/initializers/scimitar.rb +40 -3
- data/spec/apps/dummy/config/routes.rb +18 -1
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +2 -0
- data/spec/apps/dummy/db/schema.rb +3 -1
- data/spec/controllers/scimitar/application_controller_spec.rb +56 -2
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +8 -4
- data/spec/controllers/scimitar/schemas_controller_spec.rb +344 -48
- data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +1 -0
- data/spec/models/scimitar/complex_types/address_spec.rb +3 -4
- data/spec/models/scimitar/lists/query_parser_spec.rb +70 -0
- data/spec/models/scimitar/resources/base_spec.rb +55 -13
- data/spec/models/scimitar/resources/base_validation_spec.rb +16 -3
- data/spec/models/scimitar/resources/mixin_spec.rb +781 -124
- data/spec/models/scimitar/schema/attribute_spec.rb +22 -0
- data/spec/models/scimitar/schema/user_spec.rb +2 -2
- data/spec/requests/active_record_backed_resources_controller_spec.rb +723 -40
- data/spec/requests/engine_spec.rb +75 -0
- data/spec/spec_helper.rb +10 -2
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +108 -0
- metadata +42 -34
@@ -0,0 +1,24 @@
|
|
1
|
+
# For tests only - uses custom 'save!' implementation which passes a block to
|
2
|
+
# Scimitar::ActiveRecordBackedResourcesController#save!.
|
3
|
+
#
|
4
|
+
class CustomSaveMockUsersController < Scimitar::ActiveRecordBackedResourcesController
|
5
|
+
|
6
|
+
CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR = 'Custom save-block invoked'
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def save!(_record)
|
11
|
+
super do | record |
|
12
|
+
record.update!(username: CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def storage_class
|
17
|
+
MockUser
|
18
|
+
end
|
19
|
+
|
20
|
+
def storage_scope
|
21
|
+
MockUser.all
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# For tests only - uses custom 'update' implementation which passes a block to
|
2
|
+
# Scimitar::ActiveRecordBackedResourcesController#create.
|
3
|
+
#
|
4
|
+
class CustomUpdateMockUsersController < Scimitar::ActiveRecordBackedResourcesController
|
5
|
+
|
6
|
+
OVERRIDDEN_NAME = SecureRandom.uuid
|
7
|
+
|
8
|
+
def update
|
9
|
+
super do | resource |
|
10
|
+
resource.first_name = OVERRIDDEN_NAME
|
11
|
+
resource.save!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def storage_class
|
18
|
+
MockUser
|
19
|
+
end
|
20
|
+
|
21
|
+
def storage_scope
|
22
|
+
MockUser.all
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -10,6 +10,7 @@ class MockUser < ActiveRecord::Base
|
|
10
10
|
primary_key
|
11
11
|
scim_uid
|
12
12
|
username
|
13
|
+
password
|
13
14
|
first_name
|
14
15
|
last_name
|
15
16
|
work_email_address
|
@@ -17,6 +18,7 @@ class MockUser < ActiveRecord::Base
|
|
17
18
|
work_phone_number
|
18
19
|
organization
|
19
20
|
department
|
21
|
+
manager
|
20
22
|
mock_groups
|
21
23
|
}
|
22
24
|
|
@@ -46,6 +48,8 @@ class MockUser < ActiveRecord::Base
|
|
46
48
|
id: :primary_key,
|
47
49
|
externalId: :scim_uid,
|
48
50
|
userName: :username,
|
51
|
+
password: :password,
|
52
|
+
active: :is_active,
|
49
53
|
name: {
|
50
54
|
givenName: :first_name,
|
51
55
|
familyName: :last_name
|
@@ -78,8 +82,11 @@ class MockUser < ActiveRecord::Base
|
|
78
82
|
}
|
79
83
|
},
|
80
84
|
],
|
81
|
-
groups: [
|
85
|
+
groups: [
|
82
86
|
{
|
87
|
+
# Read-only, so no :find_with key. There's no 'class' specified here
|
88
|
+
# either, to help test the "/Schemas" endpoint's reflection code.
|
89
|
+
#
|
83
90
|
list: :mock_groups,
|
84
91
|
using: {
|
85
92
|
value: :id,
|
@@ -87,13 +94,16 @@ class MockUser < ActiveRecord::Base
|
|
87
94
|
}
|
88
95
|
}
|
89
96
|
],
|
90
|
-
active: :is_active,
|
91
97
|
|
92
98
|
# Custom extension schema - see configuration in
|
93
99
|
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
94
100
|
#
|
95
101
|
organization: :organization,
|
96
102
|
department: :department,
|
103
|
+
primaryEmail: :scim_primary_email,
|
104
|
+
|
105
|
+
manager: :manager,
|
106
|
+
|
97
107
|
userGroups: [
|
98
108
|
{
|
99
109
|
list: :mock_groups,
|
@@ -122,9 +132,16 @@ class MockUser < ActiveRecord::Base
|
|
122
132
|
'groups.value' => { column: MockGroup.arel_table[:id] },
|
123
133
|
'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
124
134
|
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
125
|
-
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
|
135
|
+
'emails.type' => { ignore: true }, # We can't filter on that; it'll just search all e-mails
|
136
|
+
'primaryEmail' => { column: :scim_primary_email },
|
126
137
|
}
|
127
138
|
end
|
128
139
|
|
140
|
+
# Custom attribute reader
|
141
|
+
#
|
142
|
+
def scim_primary_email
|
143
|
+
work_email_address
|
144
|
+
end
|
145
|
+
|
129
146
|
include Scimitar::Resources::Mixin
|
130
147
|
end
|
@@ -13,6 +13,14 @@ require 'scimitar'
|
|
13
13
|
module Dummy
|
14
14
|
class Application < Rails::Application
|
15
15
|
config.load_defaults 7.0
|
16
|
+
|
17
|
+
# Silence the following under Rails 8.0:
|
18
|
+
#
|
19
|
+
# "DEPRECATION WARNING: `to_time` will always preserve the full timezone
|
20
|
+
# rather than offset of the receiver in Rails 8.1. To opt in to the new
|
21
|
+
# behavior, set `config.active_support.to_time_preserves_timezone = :zone`"
|
22
|
+
#
|
23
|
+
config.active_support.to_time_preserves_timezone = :zone
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Test app configuration.
|
2
2
|
#
|
3
|
-
# Note that as a result of https://github.com/
|
3
|
+
# Note that as a result of https://github.com/pond/scimitar/issues/48,
|
4
4
|
# tests include a custom extension of the core User schema. A shortcoming of
|
5
5
|
# some of the code from which Scimitar was originally built is that those
|
6
6
|
# extensions are done with class-level ivars, so it is largely impossible (or
|
@@ -19,16 +19,27 @@ Rails.application.config.to_prepare do
|
|
19
19
|
before_action :test_hook
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
def scim_schemas_url(options)
|
24
|
+
super(test: 1, **options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def scim_resource_type_url(options)
|
28
|
+
super(test: 1, **options)
|
29
|
+
end
|
22
30
|
end
|
23
31
|
|
24
32
|
})
|
25
33
|
|
26
34
|
module ScimSchemaExtensions
|
27
35
|
module User
|
36
|
+
|
37
|
+
# This "looks like" part of the standard Enterprise extension.
|
38
|
+
#
|
28
39
|
class Enterprise < Scimitar::Schema::Base
|
29
40
|
def initialize(options = {})
|
30
41
|
super(
|
31
|
-
name: '
|
42
|
+
name: 'EnterpriseExtendedUser',
|
32
43
|
description: 'Enterprise extension for a User',
|
33
44
|
id: self.class.id,
|
34
45
|
scim_attributes: self.class.scim_attributes
|
@@ -42,7 +53,32 @@ Rails.application.config.to_prepare do
|
|
42
53
|
def self.scim_attributes
|
43
54
|
[
|
44
55
|
Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
|
45
|
-
Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
|
56
|
+
Scimitar::Schema::Attribute.new(name: 'department', type: 'string'),
|
57
|
+
Scimitar::Schema::Attribute.new(name: 'primaryEmail', type: 'string'),
|
58
|
+
]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# In https://github.com/pond/scimitar/issues/122 we learn that with
|
63
|
+
# more than one extension, things can go wrong - so now we test with two.
|
64
|
+
#
|
65
|
+
class Manager < Scimitar::Schema::Base
|
66
|
+
def initialize(options = {})
|
67
|
+
super(
|
68
|
+
name: 'ManagementExtendedUser',
|
69
|
+
description: 'Management extension for a User',
|
70
|
+
id: self.class.id,
|
71
|
+
scim_attributes: self.class.scim_attributes
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.id
|
76
|
+
'urn:ietf:params:scim:schemas:extension:manager:1.0:User'
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.scim_attributes
|
80
|
+
[
|
81
|
+
Scimitar::Schema::Attribute.new(name: 'manager', type: 'string')
|
46
82
|
]
|
47
83
|
end
|
48
84
|
end
|
@@ -50,4 +86,5 @@ Rails.application.config.to_prepare do
|
|
50
86
|
end
|
51
87
|
|
52
88
|
Scimitar::Resources::User.extend_schema ScimSchemaExtensions::User::Enterprise
|
89
|
+
Scimitar::Resources::User.extend_schema ScimSchemaExtensions::User::Manager
|
53
90
|
end
|
@@ -17,10 +17,27 @@ Rails.application.routes.draw do
|
|
17
17
|
get 'Groups/:id', to: 'mock_groups#show'
|
18
18
|
patch 'Groups/:id', to: 'mock_groups#update'
|
19
19
|
|
20
|
-
# For testing blocks passed to ActiveRecordBackedResourcesController#
|
20
|
+
# For testing blocks passed to ActiveRecordBackedResourcesController#create,
|
21
|
+
# #update, #replace and #destroy.
|
21
22
|
#
|
23
|
+
post 'CustomCreateUsers', to: 'custom_create_mock_users#create'
|
24
|
+
patch 'CustomUpdateUsers/:id', to: 'custom_update_mock_users#update'
|
25
|
+
put 'CustomReplaceUsers/:id', to: 'custom_replace_mock_users#replace'
|
22
26
|
delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
|
23
27
|
|
28
|
+
# Needed because the auto-render of most of the above includes a 'url_for'
|
29
|
+
# call for a 'show' action, so we must include routes (implemented in the
|
30
|
+
# base class) for the "show" endpoint.
|
31
|
+
#
|
32
|
+
get 'CustomCreateUsers/:id', to: 'custom_create_mock_users#show'
|
33
|
+
get 'CustomUpdateUsers/:id', to: 'custom_update_mock_users#show'
|
34
|
+
get 'CustomReplaceUsers/:id', to: 'custom_replace_mock_users#show'
|
35
|
+
|
36
|
+
# For testing blocks passed to ActiveRecordBackedResourcesController#save!
|
37
|
+
#
|
38
|
+
post 'CustomSaveUsers', to: 'custom_save_mock_users#create'
|
39
|
+
get 'CustomSaveUsers/:id', to: 'custom_save_mock_users#show'
|
40
|
+
|
24
41
|
# For testing environment inside Scimitar::ApplicationController subclasses.
|
25
42
|
#
|
26
43
|
get 'CustomRequestVerifiers', to: 'custom_request_verifiers#index'
|
@@ -7,6 +7,7 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
7
7
|
#
|
8
8
|
t.text :scim_uid
|
9
9
|
t.text :username
|
10
|
+
t.text :password
|
10
11
|
t.text :first_name
|
11
12
|
t.text :last_name
|
12
13
|
t.text :work_email_address
|
@@ -18,6 +19,7 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
18
19
|
#
|
19
20
|
t.text :organization
|
20
21
|
t.text :department
|
22
|
+
t.text :manager
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
@@ -10,7 +10,7 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema[7.
|
13
|
+
ActiveRecord::Schema[7.1].define(version: 2021_03_08_044214) do
|
14
14
|
# These are extensions that must be enabled in order to support this database
|
15
15
|
enable_extension "plpgsql"
|
16
16
|
|
@@ -33,6 +33,7 @@ ActiveRecord::Schema[7.0].define(version: 2021_03_08_044214) do
|
|
33
33
|
t.datetime "updated_at", null: false
|
34
34
|
t.text "scim_uid"
|
35
35
|
t.text "username"
|
36
|
+
t.text "password"
|
36
37
|
t.text "first_name"
|
37
38
|
t.text "last_name"
|
38
39
|
t.text "work_email_address"
|
@@ -40,6 +41,7 @@ ActiveRecord::Schema[7.0].define(version: 2021_03_08_044214) do
|
|
40
41
|
t.text "work_phone_number"
|
41
42
|
t.text "organization"
|
42
43
|
t.text "department"
|
44
|
+
t.text "manager"
|
43
45
|
end
|
44
46
|
|
45
47
|
add_foreign_key "mock_groups_users", "mock_groups"
|
@@ -24,7 +24,7 @@ RSpec.describe Scimitar::ApplicationController do
|
|
24
24
|
get :index, params: { format: :scim }
|
25
25
|
expect(response).to be_ok
|
26
26
|
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
27
|
-
expect(response.headers['
|
27
|
+
expect(response.headers['WWW-Authenticate']).to eql('Basic')
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'renders failure with bad password' do
|
@@ -84,7 +84,61 @@ RSpec.describe Scimitar::ApplicationController do
|
|
84
84
|
get :index, params: { format: :scim }
|
85
85
|
expect(response).to be_ok
|
86
86
|
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
87
|
-
expect(response.headers['
|
87
|
+
expect(response.headers['WWW-Authenticate']).to eql('Bearer')
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'renders failure with bad token' do
|
91
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer Invalid'
|
92
|
+
|
93
|
+
get :index, params: { format: :scim }
|
94
|
+
expect(response).not_to be_ok
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'renders failure with blank token' do
|
98
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer'
|
99
|
+
|
100
|
+
get :index, params: { format: :scim }
|
101
|
+
expect(response).not_to be_ok
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'renders failure with missing header' do
|
105
|
+
get :index, params: { format: :scim }
|
106
|
+
expect(response).not_to be_ok
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'authenticator evaluated within controller context' do
|
111
|
+
|
112
|
+
# Define a controller with a custom instance method 'valid_token'.
|
113
|
+
#
|
114
|
+
controller do
|
115
|
+
def index
|
116
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_token
|
120
|
+
'B'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Call the above controller method from the token authenticator Proc,
|
125
|
+
# proving that it was executed in the controller's context.
|
126
|
+
#
|
127
|
+
before do
|
128
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new(
|
129
|
+
token_authenticator: Proc.new do | token, options |
|
130
|
+
token == self.valid_token()
|
131
|
+
end
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'renders success when valid creds are given' do
|
136
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer B'
|
137
|
+
|
138
|
+
get :index, params: { format: :scim }
|
139
|
+
expect(response).to be_ok
|
140
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
141
|
+
expect(response.headers['WWW-Authenticate']).to eql('Bearer')
|
88
142
|
end
|
89
143
|
|
90
144
|
it 'renders failure with bad token' do
|
@@ -9,11 +9,15 @@ RSpec.describe Scimitar::ResourceTypesController do
|
|
9
9
|
it 'renders the resource type for user' do
|
10
10
|
get :index, format: :scim
|
11
11
|
response_hash = JSON.parse(response.body)
|
12
|
-
expected_response =
|
13
|
-
|
14
|
-
|
12
|
+
expected_response = {
|
13
|
+
schemas: ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
|
14
|
+
totalResults: 2,
|
15
|
+
Resources: [
|
16
|
+
Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User', test: 1)),
|
17
|
+
Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group', test: 1))
|
18
|
+
]
|
19
|
+
}.to_json
|
15
20
|
|
16
|
-
response_hash = JSON.parse(response.body)
|
17
21
|
expect(response_hash).to eql(JSON.parse(expected_response))
|
18
22
|
end
|
19
23
|
|