scimitar 1.3.1 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9faabfad9cae931ab19f765393d9f307fa5d44773f943d2286fbd92302c2e9e1
4
- data.tar.gz: 2bc23d4e3b123103efd0fa87375547dcdf9e3684ea272a1e4941c43e7845937b
3
+ metadata.gz: cd0efbdecece6297062b46cf46bdeaccd8330cd58fc94fb2bcb0de7c7bb90806
4
+ data.tar.gz: 7f3528ee080f0f295d03c77fba1586484b5cadd6ef5e4de1fc00d7807ec7a0c1
5
5
  SHA512:
6
- metadata.gz: bb74810f72d7b806b7e6aef1a02d363be1135dbf190e2ae0e88a727f94ec9b90e643a13883cd64951d4879fe542fb5936621ec96c44d0f192050f9f6453b58ef
7
- data.tar.gz: b8ea496f490696379236a971070f6e04188fbfcff27f0dd484e18c01416193c625173c72618e123cbc39b1521ef4e6137553396d981db6366861f98f172cee2c
6
+ metadata.gz: 8044cc09d7b964401b09e6fe8ccfa272b6c67ab97687c79a151010a447109f9e671bb864094905b2495100591e2472f673ce2b977fc07f2a13213a8d4fbc75b1
7
+ data.tar.gz: 51a5522fcf948d3b9f19c6bc5b28732f0fb4537034106d47dca83d7d043d5749d9adf1c31acf10fc7eb79a63739787a0ec42f0a261eb293072634aabca0ec92d
@@ -21,6 +21,8 @@ module Scimitar
21
21
 
22
22
  rescue_from ActiveRecord::RecordNotFound, with: :handle_resource_not_found # See Scimitar::ApplicationController
23
23
 
24
+ before_action :obtain_id_column_name_from_attribute_map
25
+
24
26
  # GET (list)
25
27
  #
26
28
  def index
@@ -37,12 +39,13 @@ module Scimitar
37
39
  pagination_info = scim_pagination_info(query.count())
38
40
 
39
41
  page_of_results = query
42
+ .order(@id_column => :asc)
40
43
  .offset(pagination_info.offset)
41
44
  .limit(pagination_info.limit)
42
45
  .to_a()
43
46
 
44
47
  super(pagination_info, page_of_results) do | record |
45
- record.to_scim(location: url_for(action: :show, id: record.id))
48
+ record_to_scim(record)
46
49
  end
47
50
  end
48
51
 
@@ -51,7 +54,7 @@ module Scimitar
51
54
  def show
52
55
  super do |record_id|
53
56
  record = self.find_record(record_id)
54
- record.to_scim(location: url_for(action: :show, id: record_id))
57
+ record_to_scim(record)
55
58
  end
56
59
  end
57
60
 
@@ -63,7 +66,7 @@ module Scimitar
63
66
  record = self.storage_class().new
64
67
  record.from_scim!(scim_hash: scim_resource.as_json())
65
68
  self.save!(record)
66
- record.to_scim(location: url_for(action: :show, id: record.id))
69
+ record_to_scim(record)
67
70
  end
68
71
  end
69
72
  end
@@ -76,7 +79,7 @@ module Scimitar
76
79
  record = self.find_record(record_id)
77
80
  record.from_scim!(scim_hash: scim_resource.as_json())
78
81
  self.save!(record)
79
- record.to_scim(location: url_for(action: :show, id: record.id))
82
+ record_to_scim(record)
80
83
  end
81
84
  end
82
85
  end
@@ -89,7 +92,7 @@ module Scimitar
89
92
  record = self.find_record(record_id)
90
93
  record.from_scim_patch!(patch_hash: patch_hash)
91
94
  self.save!(record)
92
- record.to_scim(location: url_for(action: :show, id: record.id))
95
+ record_to_scim(record)
93
96
  end
94
97
  end
95
98
  end
@@ -137,7 +140,14 @@ module Scimitar
137
140
  # +record_id+:: Record ID (SCIM schema 'id' value - "our" ID).
138
141
  #
139
142
  def find_record(record_id)
140
- self.storage_scope().find(record_id)
143
+ self.storage_scope().find_by!(@id_column => record_id)
144
+ end
145
+
146
+ # DRY up controller actions - pass a record; returns the SCIM
147
+ # representation, with a "show" location specified via #url_for.
148
+ #
149
+ def record_to_scim(record)
150
+ record.to_scim(location: url_for(action: :show, id: record.send(@id_column)))
141
151
  end
142
152
 
143
153
  # Save a record, dealing with validation exceptions by raising SCIM
@@ -176,5 +186,16 @@ module Scimitar
176
186
  end
177
187
  end
178
188
 
189
+ # Called via +before_action+ - stores in @id_column the name of whatever
190
+ # model column is used to store the record ID, via
191
+ # Scimitar::Resources::Mixin::scim_attributes_map.
192
+ #
193
+ # Default is <tt>:id</tt>.
194
+ #
195
+ def obtain_id_column_name_from_attribute_map
196
+ attrs = storage_class().scim_attributes_map() || {}
197
+ @id_column = attrs[:id] || :id
198
+ end
199
+
179
200
  end
180
201
  end
@@ -78,7 +78,7 @@ module Scimitar
78
78
  # method's return value here.
79
79
  #
80
80
  def initialize(attribute_map)
81
- @attribute_map = attribute_map
81
+ @attribute_map = attribute_map.with_indifferent_case_insensitive_access()
82
82
  end
83
83
 
84
84
  # Parse SCIM filter query into RPN stack
@@ -605,6 +605,16 @@ module Scimitar
605
605
 
606
606
  raise Scimitar::FilterError unless all_supported
607
607
 
608
+ unless case_sensitive
609
+ lc_scim_attribute = scim_attribute.downcase()
610
+
611
+ case_sensitive = (
612
+ lc_scim_attribute == 'id' ||
613
+ lc_scim_attribute == 'externalid' ||
614
+ lc_scim_attribute.start_with?('meta.')
615
+ )
616
+ end
617
+
608
618
  column_names.each.with_index do | column_name, index |
609
619
  arel_column = arel_table[column_name]
610
620
  arel_operation = case scim_operator
@@ -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 = '1.3.1'
6
+ VERSION = '1.4.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 = '2022-11-04'
11
+ DATE = '2023-01-26'
12
12
 
13
13
  end
@@ -1,4 +1,4 @@
1
- class MockUsersController < Scimitar::ActiveRecordBackedResourcesController
1
+ class MockGroupsController < Scimitar::ActiveRecordBackedResourcesController
2
2
 
3
3
  protected
4
4
 
@@ -58,7 +58,7 @@ class MockGroup < ActiveRecord::Base
58
58
 
59
59
  case type.downcase
60
60
  when 'user'
61
- MockUser.find_by_id(id)
61
+ MockUser.find_by_primary_key(id)
62
62
  when 'group'
63
63
  MockGroup.find_by_id(id)
64
64
  else
@@ -5,7 +5,7 @@ class MockUser < ActiveRecord::Base
5
5
  # ===========================================================================
6
6
 
7
7
  READWRITE_ATTRS = %w{
8
- id
8
+ primary_key
9
9
  scim_uid
10
10
  username
11
11
  first_name
@@ -38,7 +38,7 @@ class MockUser < ActiveRecord::Base
38
38
 
39
39
  def self.scim_attributes_map
40
40
  return {
41
- id: :id,
41
+ id: :primary_key,
42
42
  externalId: :scim_uid,
43
43
  userName: :username,
44
44
  name: {
@@ -92,11 +92,14 @@ class MockUser < ActiveRecord::Base
92
92
 
93
93
  def self.scim_queryable_attributes
94
94
  return {
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
95
+ 'id' => { column: :primary_key },
96
+ 'externalId' => { column: :scim_uid },
97
+ 'meta.lastModified' => { column: :updated_at },
98
+ 'name.givenName' => { column: :first_name },
99
+ 'name.familyName' => { column: :last_name },
100
+ 'emails' => { columns: [ :work_email_address, :home_email_address ] },
101
+ 'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
102
+ 'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
100
103
  }
101
104
  end
102
105
 
@@ -13,6 +13,9 @@ 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
+
16
19
  # For testing blocks passed to ActiveRecordBackedResourcesController#destroy
17
20
  #
18
21
  delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
@@ -1,6 +1,7 @@
1
1
  class CreateMockUsers < ActiveRecord::Migration[6.1]
2
2
  def change
3
- create_table :mock_users do |t|
3
+ create_table :mock_users, id: :uuid, primary_key: :primary_key do |t|
4
+ t.timestamps
4
5
 
5
6
  t.text :scim_uid
6
7
  t.text :username
@@ -9,7 +10,6 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
9
10
  t.text :work_email_address
10
11
  t.text :home_email_address
11
12
  t.text :work_phone_number
12
-
13
13
  end
14
14
  end
15
15
  end
@@ -1,8 +1,13 @@
1
1
  class CreateJoinTableMockGroupsMockUsers < ActiveRecord::Migration[6.1]
2
2
  def change
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]
3
+ create_table :mock_groups_users, id: false do | t |
4
+ t.references :mock_group, foreign_key: true, type: :int8, index: true, null: false
5
+ t.references :mock_user, type: :uuid, index: true, null: false, primary_key: :primary_key
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
6
11
  end
7
12
  end
8
13
  end
@@ -24,12 +24,14 @@ 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.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"
27
+ t.uuid "mock_user_id", null: false
28
+ t.index ["mock_group_id"], name: "index_mock_groups_users_on_mock_group_id"
29
+ t.index ["mock_user_id"], name: "index_mock_groups_users_on_mock_user_id"
30
30
  end
31
31
 
32
- create_table "mock_users", force: :cascade do |t|
32
+ create_table "mock_users", primary_key: "primary_key", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
33
+ t.datetime "created_at", precision: 6, null: false
34
+ t.datetime "updated_at", precision: 6, null: false
33
35
  t.text "scim_uid"
34
36
  t.text "username"
35
37
  t.text "first_name"
@@ -39,4 +41,6 @@ ActiveRecord::Schema.define(version: 2021_03_08_044214) do
39
41
  t.text "work_phone_number"
40
42
  end
41
43
 
44
+ add_foreign_key "mock_groups_users", "mock_groups"
45
+ add_foreign_key "mock_groups_users", "mock_users", primary_key: "primary_key"
42
46
  end
@@ -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(:id)).to eql([user_1.id])
408
+ expect(query.pluck(:primary_key)).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(:id)).to match_array([user_1.id, user_2.id])
414
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:id)).to eql([user_2.id])
420
+ expect(query.pluck(:primary_key)).to eql([user_2.primary_key])
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(:id)).to match_array([user_1.id, user_2.id])
428
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:id)).to match_array([user_1.id])
438
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key])
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(:id)).to match_array([user_2.id])
502
+ expect(query.pluck(:primary_key)).to match_array([user_2.primary_key])
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(:id)).to match_array([user_1.id, user_2.id])
523
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:id)).to match_array([user_1.id, user_2.id, user_3.id])
549
+ expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key, user_3.primary_key])
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, 'externalId') }.to raise_error(Scimitar::FilterError)
593
+ expect { @instance.send(:activerecord_columns, 'userName') }.to raise_error(Scimitar::FilterError)
594
594
  end
595
595
 
596
596
  it 'complains about malformed declarations' do
@@ -159,41 +159,67 @@ RSpec.describe Scimitar::Resources::Mixin do
159
159
  # =========================================================================
160
160
 
161
161
  context '#to_scim' do
162
- it 'compiles instance attribute values into a SCIM representation' do
163
- instance = MockUser.new
164
- instance.id = 42
165
- instance.scim_uid = 'AA02984'
166
- instance.username = 'foo'
167
- instance.first_name = 'Foo'
168
- instance.last_name = 'Bar'
169
- instance.work_email_address = 'foo.bar@test.com'
170
- instance.home_email_address = nil
171
- instance.work_phone_number = '+642201234567'
172
-
173
- g1 = MockGroup.create!(display_name: 'Group 1')
174
- g2 = MockGroup.create!(display_name: 'Group 2')
175
- g3 = MockGroup.create!(display_name: 'Group 3')
176
-
177
- g1.mock_users << instance
178
- g3.mock_users << instance
179
-
180
- scim = instance.to_scim(location: 'https://test.com/mock_users/42')
181
- json = scim.to_json()
182
- hash = JSON.parse(json)
183
-
184
- expect(hash).to eql({
185
- 'userName' => 'foo',
186
- 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
187
- 'active' => true,
188
- 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
189
- 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
190
- 'id' => '42', # Note, String
191
- 'externalId' => 'AA02984',
192
- 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
193
- 'meta' => {'location'=>'https://test.com/mock_users/42', 'resourceType'=>'User'},
194
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
195
- })
196
- end
162
+ context 'with a UUID, renamed primary key column' do
163
+ it 'compiles instance attribute values into a SCIM representation' do
164
+ uuid = SecureRandom.uuid
165
+
166
+ instance = MockUser.new
167
+ instance.primary_key = uuid
168
+ instance.scim_uid = 'AA02984'
169
+ instance.username = 'foo'
170
+ instance.first_name = 'Foo'
171
+ instance.last_name = 'Bar'
172
+ instance.work_email_address = 'foo.bar@test.com'
173
+ instance.home_email_address = nil
174
+ instance.work_phone_number = '+642201234567'
175
+
176
+ g1 = MockGroup.create!(display_name: 'Group 1')
177
+ g2 = MockGroup.create!(display_name: 'Group 2')
178
+ g3 = MockGroup.create!(display_name: 'Group 3')
179
+
180
+ g1.mock_users << instance
181
+ g3.mock_users << instance
182
+
183
+ scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}")
184
+ json = scim.to_json()
185
+ hash = JSON.parse(json)
186
+
187
+ expect(hash).to eql({
188
+ 'userName' => 'foo',
189
+ 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
190
+ 'active' => true,
191
+ 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
192
+ 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
193
+ 'id' => uuid,
194
+ 'externalId' => 'AA02984',
195
+ 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
196
+ 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
197
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
198
+ })
199
+ end
200
+ end # "context 'with a UUID, renamed primary key column' do"
201
+
202
+ context 'with an integer, conventionally named primary key column' do
203
+ it 'compiles instance attribute values into a SCIM representation' do
204
+ instance = MockGroup.new
205
+ instance.id = 42
206
+ instance.scim_uid = 'GG02984'
207
+ instance.display_name = 'Some group'
208
+
209
+ scim = instance.to_scim(location: 'https://test.com/mock_groups/42')
210
+ json = scim.to_json()
211
+ hash = JSON.parse(json)
212
+
213
+ expect(hash).to eql({
214
+ 'displayName' => 'Some group',
215
+ 'id' => '42', # Note, String
216
+ 'externalId' => 'GG02984',
217
+ 'members' => [],
218
+ 'meta' => {'location'=>'https://test.com/mock_groups/42', 'resourceType'=>'Group'},
219
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group']
220
+ })
221
+ end
222
+ end # "context 'with an integer, conventionally named primary key column' do"
197
223
 
198
224
  context 'with optional timestamps' do
199
225
  context 'creation only' do
@@ -405,8 +431,8 @@ RSpec.describe Scimitar::Resources::Mixin do
405
431
  'displayName' => 'Foo Group',
406
432
  'members' => [
407
433
  {'type' => 'Group', 'value' => g1.id.to_s},
408
- {'type' => 'User', 'value' => u1.id.to_s},
409
- {'type' => 'User', 'value' => u3.id.to_s}
434
+ {'type' => 'User', 'value' => u1.primary_key.to_s},
435
+ {'type' => 'User', 'value' => u3.primary_key.to_s}
410
436
  ],
411
437
  'externalId' => 'GG01536',
412
438
  'meta' => {'location'=>'https://test.com/mock_groups/1', 'resourceType'=>'Group'},
@@ -453,8 +479,10 @@ RSpec.describe Scimitar::Resources::Mixin do
453
479
  end # "context 'using upper case' do"
454
480
 
455
481
  it 'clears things not present in input' do
482
+ uuid = SecureRandom.uuid
483
+
456
484
  instance = MockUser.new
457
- instance.id = 42
485
+ instance.primary_key = uuid
458
486
  instance.scim_uid = 'AA02984'
459
487
  instance.username = 'foo'
460
488
  instance.first_name = 'Foo'
@@ -465,7 +493,7 @@ RSpec.describe Scimitar::Resources::Mixin do
465
493
 
466
494
  instance.from_scim!(scim_hash: {})
467
495
 
468
- expect(instance.id ).to eql(42)
496
+ expect(instance.primary_key ).to eql(uuid)
469
497
  expect(instance.scim_uid ).to be_nil
470
498
  expect(instance.username ).to be_nil
471
499
  expect(instance.first_name ).to be_nil
@@ -42,13 +42,13 @@ RSpec.describe Scimitar::Resources::User do
42
42
  let(:user) { described_class.new }
43
43
 
44
44
  it 'adds the error when the value is a string' do
45
- user.add_errors_from_hash(key: 'some error')
45
+ user.add_errors_from_hash({key: 'some error'})
46
46
  expect(user.errors.messages.to_h).to eql({key: ['some error']})
47
47
  expect(user.errors.full_messages).to eql(['Key some error'])
48
48
  end
49
49
 
50
50
  it 'adds the error when the value is an array' do
51
- user.add_errors_from_hash(key: ['error1', 'error2'])
51
+ user.add_errors_from_hash({key: ['error1', 'error2']})
52
52
  expect(user.errors.messages.to_h).to eql({key: ['error1', 'error2']})
53
53
  expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
54
54
  end
@@ -21,10 +21,8 @@ RSpec.describe Scimitar::Schema::Attribute do
21
21
  expect(name.type).to eql('complex')
22
22
  expect(name.subAttributes).to eql(Scimitar::Schema::Name.scim_attributes)
23
23
  end
24
-
25
24
  end
26
25
 
27
-
28
26
  context '#valid?' do
29
27
  it 'is invalid if attribute is required but value is blank' do
30
28
  attribute = described_class.new(name: 'userName', type: 'string', required: true)
@@ -76,5 +74,4 @@ RSpec.describe Scimitar::Schema::Attribute do
76
74
  expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('gaga')).to be(false)
77
75
  end
78
76
  end
79
-
80
77
  end
@@ -1,12 +1,27 @@
1
1
  require 'spec_helper'
2
+ require 'time'
2
3
 
3
4
  RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
4
5
  before :each do
5
6
  allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
6
7
 
7
- @u1 = MockUser.create(username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com')
8
- @u2 = MockUser.create(username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com')
9
- @u3 = MockUser.create(username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com')
8
+ lmt = Time.parse("2023-01-09 14:25:00 +1300")
9
+
10
+ # If a sort order is unspecified, the controller defaults to ID ascending.
11
+ # With UUID based IDs, testing life is made easier by ensuring that the
12
+ # creation order matches an ascending UUID sort order (which is what would
13
+ # happen if we were using integer primary keys).
14
+ #
15
+ lmt = Time.parse("2023-01-09 14:25:00 +1300")
16
+ ids = 3.times.map { SecureRandom.uuid }.sort()
17
+
18
+ @u1 = MockUser.create(primary_key: ids.shift(), username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com', scim_uid: '001', created_at: lmt, updated_at: lmt + 1)
19
+ @u2 = MockUser.create(primary_key: ids.shift(), username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com', scim_uid: '002', created_at: lmt, updated_at: lmt + 2)
20
+ @u3 = MockUser.create(primary_key: ids.shift(), username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
21
+
22
+ @g1 = MockGroup.create!(display_name: 'Group 1')
23
+ @g2 = MockGroup.create!(display_name: 'Group 2')
24
+ @g3 = MockGroup.create!(display_name: 'Group 3')
10
25
  end
11
26
 
12
27
  # ===========================================================================
@@ -29,26 +44,65 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
29
44
  end # "context 'with no items' do"
30
45
 
31
46
  context 'with items' do
32
- it 'returns all items' do
33
- get '/Users', params: { format: :scim }
47
+ context 'with a UUID, renamed primary key column' do
48
+ it 'returns all items' do
49
+ get '/Users', params: { format: :scim }
50
+
51
+ expect(response.status).to eql(200)
52
+ result = JSON.parse(response.body)
53
+
54
+ expect(result['totalResults']).to eql(3)
55
+ expect(result['Resources'].size).to eql(3)
56
+
57
+ ids = result['Resources'].map { |resource| resource['id'] }
58
+ expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
59
+
60
+ usernames = result['Resources'].map { |resource| resource['userName'] }
61
+ expect(usernames).to match_array(['1', '2', '3'])
62
+ end
63
+ end # "context 'with a UUID, renamed primary key column' do"
64
+
65
+ context 'with an integer, conventionally named primary key column' do
66
+ it 'returns all items' do
67
+ get '/Groups', params: { format: :scim }
68
+
69
+ expect(response.status).to eql(200)
70
+ result = JSON.parse(response.body)
71
+
72
+ expect(result['totalResults']).to eql(3)
73
+ expect(result['Resources'].size).to eql(3)
74
+
75
+ ids = result['Resources'].map { |resource| resource['id'] }
76
+ expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
77
+
78
+ usernames = result['Resources'].map { |resource| resource['displayName'] }
79
+ expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
80
+ end
81
+ end # "context 'with an integer, conventionally named primary key column' do"
82
+
83
+ it 'applies a filter, with case-insensitive value comparison' do
84
+ get '/Users', params: {
85
+ format: :scim,
86
+ filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
87
+ }
34
88
 
35
89
  expect(response.status).to eql(200)
36
90
  result = JSON.parse(response.body)
37
91
 
38
- expect(result['totalResults']).to eql(3)
39
- expect(result['Resources'].size).to eql(3)
92
+ expect(result['totalResults']).to eql(1)
93
+ expect(result['Resources'].size).to eql(1)
40
94
 
41
95
  ids = result['Resources'].map { |resource| resource['id'] }
42
- expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s, @u3.id.to_s])
96
+ expect(ids).to match_array([@u2.primary_key.to_s])
43
97
 
44
98
  usernames = result['Resources'].map { |resource| resource['userName'] }
45
- expect(usernames).to match_array(['1', '2', '3'])
99
+ expect(usernames).to match_array(['2'])
46
100
  end
47
101
 
48
- it 'applies a filter, with case-insensitive value comparison' do
102
+ it 'applies a filter, with case-insensitive attribute matching (GitHub issue #37)' do
49
103
  get '/Users', params: {
50
104
  format: :scim,
51
- filter: 'name.givenName eq "Foo" and name.familyName pr and emails ne "home_1@TEST.COM"'
105
+ filter: 'name.GIVENNAME eq "Foo" and name.Familyname pr and emails ne "home_1@test.com"'
52
106
  }
53
107
 
54
108
  expect(response.status).to eql(200)
@@ -58,12 +112,74 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
58
112
  expect(result['Resources'].size).to eql(1)
59
113
 
60
114
  ids = result['Resources'].map { |resource| resource['id'] }
61
- expect(ids).to match_array([@u2.id.to_s])
115
+ expect(ids).to match_array([@u2.primary_key.to_s])
62
116
 
63
117
  usernames = result['Resources'].map { |resource| resource['userName'] }
64
118
  expect(usernames).to match_array(['2'])
65
119
  end
66
120
 
121
+ # Strange attribute capitalisation in tests here builds on test coverage
122
+ # for now-fixed GitHub issue #37.
123
+ #
124
+ context '"meta" / IDs (GitHub issue #36)' do
125
+ it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
126
+ get '/Users', params: {
127
+ format: :scim,
128
+ filter: "id eq \"#{@u3.primary_key}\""
129
+ }
130
+
131
+ expect(response.status).to eql(200)
132
+ result = JSON.parse(response.body)
133
+
134
+ expect(result['totalResults']).to eql(1)
135
+ expect(result['Resources'].size).to eql(1)
136
+
137
+ ids = result['Resources'].map { |resource| resource['id'] }
138
+ expect(ids).to match_array([@u3.primary_key.to_s])
139
+
140
+ usernames = result['Resources'].map { |resource| resource['userName'] }
141
+ expect(usernames).to match_array(['3'])
142
+ end
143
+
144
+ it 'applies a filter on external IDs, using direct comparison' do
145
+ get '/Users', params: {
146
+ format: :scim,
147
+ filter: "externalID eq \"#{@u2.scim_uid}\""
148
+ }
149
+
150
+ expect(response.status).to eql(200)
151
+ result = JSON.parse(response.body)
152
+
153
+ expect(result['totalResults']).to eql(1)
154
+ expect(result['Resources'].size).to eql(1)
155
+
156
+ ids = result['Resources'].map { |resource| resource['id'] }
157
+ expect(ids).to match_array([@u2.primary_key.to_s])
158
+
159
+ usernames = result['Resources'].map { |resource| resource['userName'] }
160
+ expect(usernames).to match_array(['2'])
161
+ end
162
+
163
+ it 'applies a filter on "meta" entries, using direct comparison' do
164
+ get '/Users', params: {
165
+ format: :scim,
166
+ filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
167
+ }
168
+
169
+ expect(response.status).to eql(200)
170
+ result = JSON.parse(response.body)
171
+
172
+ expect(result['totalResults']).to eql(1)
173
+ expect(result['Resources'].size).to eql(1)
174
+
175
+ ids = result['Resources'].map { |resource| resource['id'] }
176
+ expect(ids).to match_array([@u3.primary_key.to_s])
177
+
178
+ usernames = result['Resources'].map { |resource| resource['userName'] }
179
+ expect(usernames).to match_array(['3'])
180
+ end
181
+ end # "context '"meta" / IDs (GitHub issue #36)' do"
182
+
67
183
  it 'obeys a page size' do
68
184
  get '/Users', params: {
69
185
  format: :scim,
@@ -77,7 +193,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
77
193
  expect(result['Resources'].size).to eql(2)
78
194
 
79
195
  ids = result['Resources'].map { |resource| resource['id'] }
80
- expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s])
196
+ expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s])
81
197
 
82
198
  usernames = result['Resources'].map { |resource| resource['userName'] }
83
199
  expect(usernames).to match_array(['1', '2'])
@@ -96,7 +212,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
96
212
  expect(result['Resources'].size).to eql(2)
97
213
 
98
214
  ids = result['Resources'].map { |resource| resource['id'] }
99
- expect(ids).to match_array([@u2.id.to_s, @u3.id.to_s])
215
+ expect(ids).to match_array([@u2.primary_key.to_s, @u3.primary_key.to_s])
100
216
 
101
217
  usernames = result['Resources'].map { |resource| resource['userName'] }
102
218
  expect(usernames).to match_array(['2', '3'])
@@ -120,18 +236,34 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
120
236
  # ===========================================================================
121
237
 
122
238
  context '#show' do
123
- it 'shows an item' do
124
- expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
125
- get "/Users/#{@u2.id}", params: { format: :scim }
239
+ context 'with a UUID, renamed primary key column' do
240
+ it 'shows an item' do
241
+ expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
242
+ get "/Users/#{@u2.primary_key}", params: { format: :scim }
126
243
 
127
- expect(response.status).to eql(200)
128
- result = JSON.parse(response.body)
244
+ expect(response.status).to eql(200)
245
+ result = JSON.parse(response.body)
129
246
 
130
- expect(result['id']).to eql(@u2.id.to_s) # Note - ID was converted String; not Integer
131
- expect(result['userName']).to eql('2')
132
- expect(result['name']['familyName']).to eql('Bar')
133
- expect(result['meta']['resourceType']).to eql('User')
134
- end
247
+ expect(result['id']).to eql(@u2.primary_key.to_s)
248
+ expect(result['userName']).to eql('2')
249
+ expect(result['name']['familyName']).to eql('Bar')
250
+ expect(result['meta']['resourceType']).to eql('User')
251
+ end
252
+ end # "context 'with a UUID, renamed primary key column' do"
253
+
254
+ context 'with an integer, conventionally named primary key column' do
255
+ it 'shows an item' do
256
+ expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
257
+ get "/Groups/#{@g2.id}", params: { format: :scim }
258
+
259
+ expect(response.status).to eql(200)
260
+ result = JSON.parse(response.body)
261
+
262
+ expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
263
+ expect(result['displayName']).to eql('Group 2')
264
+ expect(result['meta']['resourceType']).to eql('Group')
265
+ end
266
+ end # "context 'with an integer, conventionally named primary key column' do"
135
267
 
136
268
  it 'renders 404' do
137
269
  get '/Users/xyz', params: { format: :scim }
@@ -164,7 +296,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
164
296
  expect(response.status).to eql(201)
165
297
  result = JSON.parse(response.body)
166
298
 
167
- expect(result['id']).to eql(new_mock.id.to_s)
299
+ expect(result['id']).to eql(new_mock.primary_key.to_s)
168
300
  expect(result['meta']['resourceType']).to eql('User')
169
301
  expect(new_mock.username).to eql('4')
170
302
  end
@@ -205,7 +337,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
205
337
  expect(response.status).to eql(201)
206
338
  result = JSON.parse(response.body)
207
339
 
208
- expect(result['id']).to eql(new_mock.id.to_s)
340
+ expect(result['id']).to eql(new_mock.primary_key.to_s)
209
341
  expect(result['meta']['resourceType']).to eql('User')
210
342
  expect(new_mock.username).to eql('4')
211
343
  expect(new_mock.first_name).to eql('Given')
@@ -279,13 +411,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
279
411
 
280
412
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
281
413
  expect {
282
- put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
414
+ put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
283
415
  }.to_not change { MockUser.count }
284
416
 
285
417
  expect(response.status).to eql(200)
286
418
  result = JSON.parse(response.body)
287
419
 
288
- expect(result['id']).to eql(@u2.id.to_s)
420
+ expect(result['id']).to eql(@u2.primary_key.to_s)
289
421
  expect(result['meta']['resourceType']).to eql('User')
290
422
 
291
423
  @u2.reload
@@ -307,7 +439,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
307
439
 
308
440
  it 'notes schema validation failures' do
309
441
  expect {
310
- put "/Users/#{@u2.id}", params: {
442
+ put "/Users/#{@u2.primary_key}", params: {
311
443
  format: :scim
312
444
  # userName parameter is required by schema, but missing
313
445
  }
@@ -386,13 +518,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
386
518
 
387
519
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
388
520
  expect {
389
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
521
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
390
522
  }.to_not change { MockUser.count }
391
523
 
392
524
  expect(response.status).to eql(200)
393
525
  result = JSON.parse(response.body)
394
526
 
395
- expect(result['id']).to eql(@u2.id.to_s)
527
+ expect(result['id']).to eql(@u2.primary_key.to_s)
396
528
  expect(result['meta']['resourceType']).to eql('User')
397
529
 
398
530
  @u2.reload
@@ -423,13 +555,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
423
555
 
424
556
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
425
557
  expect {
426
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
558
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
427
559
  }.to_not change { MockUser.count }
428
560
 
429
561
  expect(response.status).to eql(200)
430
562
  result = JSON.parse(response.body)
431
563
 
432
- expect(result['id']).to eql(@u2.id.to_s)
564
+ expect(result['id']).to eql(@u2.primary_key.to_s)
433
565
  expect(result['meta']['resourceType']).to eql('User')
434
566
 
435
567
  @u2.reload
@@ -455,13 +587,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
455
587
 
456
588
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
457
589
  expect {
458
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
590
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
459
591
  }.to_not change { MockUser.count }
460
592
 
461
593
  expect(response.status).to eql(200)
462
594
  result = JSON.parse(response.body)
463
595
 
464
- expect(result['id']).to eql(@u2.id.to_s)
596
+ expect(result['id']).to eql(@u2.primary_key.to_s)
465
597
  expect(result['meta']['resourceType']).to eql('User')
466
598
 
467
599
  @u2.reload
@@ -487,13 +619,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
487
619
 
488
620
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
489
621
  expect {
490
- patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
622
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
491
623
  }.to_not change { MockUser.count }
492
624
 
493
625
  expect(response.status).to eql(200)
494
626
  result = JSON.parse(response.body)
495
627
 
496
- expect(result['id']).to eql(@u2.id.to_s)
628
+ expect(result['id']).to eql(@u2.primary_key.to_s)
497
629
  expect(result['meta']['resourceType']).to eql('User')
498
630
 
499
631
  @u2.reload
@@ -517,7 +649,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
517
649
 
518
650
  it 'notes Rails validation failures' do
519
651
  expect {
520
- patch "/Users/#{@u2.id}", params: {
652
+ patch "/Users/#{@u2.primary_key}", params: {
521
653
  format: :scim,
522
654
  Operations: [
523
655
  {
@@ -570,7 +702,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
570
702
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
571
703
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
572
704
  expect {
573
- delete "/Users/#{@u2.id}", params: { format: :scim }
705
+ delete "/Users/#{@u2.primary_key}", params: { format: :scim }
574
706
  }.to change { MockUser.count }.by(-1)
575
707
 
576
708
  expect(response.status).to eql(204)
@@ -582,7 +714,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
582
714
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
583
715
 
584
716
  expect {
585
- delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
717
+ delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
586
718
  }.to_not change { MockUser.count }
587
719
 
588
720
  expect(response.status).to eql(204)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scimitar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RIPA Global
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-11-04 00:00:00.000000000 Z
12
+ date: 2023-01-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -260,51 +260,51 @@ required_rubygems_version: !ruby/object:Gem::Requirement
260
260
  - !ruby/object:Gem::Version
261
261
  version: '0'
262
262
  requirements: []
263
- rubygems_version: 3.1.6
263
+ rubygems_version: 3.4.4
264
264
  signing_key:
265
265
  specification_version: 4
266
266
  summary: SCIM v2 for Rails
267
267
  test_files:
268
- - spec/spec_helper.rb
269
- - spec/models/scimitar/lists/query_parser_spec.rb
268
+ - spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
269
+ - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
270
+ - spec/apps/dummy/app/controllers/mock_groups_controller.rb
271
+ - spec/apps/dummy/app/controllers/mock_users_controller.rb
272
+ - spec/apps/dummy/app/models/mock_group.rb
273
+ - spec/apps/dummy/app/models/mock_user.rb
274
+ - spec/apps/dummy/config/application.rb
275
+ - spec/apps/dummy/config/boot.rb
276
+ - spec/apps/dummy/config/environment.rb
277
+ - spec/apps/dummy/config/environments/test.rb
278
+ - spec/apps/dummy/config/initializers/cookies_serializer.rb
279
+ - spec/apps/dummy/config/initializers/scimitar.rb
280
+ - spec/apps/dummy/config/initializers/session_store.rb
281
+ - spec/apps/dummy/config/routes.rb
282
+ - spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb
283
+ - spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb
284
+ - spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb
285
+ - spec/apps/dummy/db/schema.rb
286
+ - spec/controllers/scimitar/application_controller_spec.rb
287
+ - spec/controllers/scimitar/resource_types_controller_spec.rb
288
+ - spec/controllers/scimitar/resources_controller_spec.rb
289
+ - spec/controllers/scimitar/schemas_controller_spec.rb
290
+ - spec/controllers/scimitar/service_provider_configurations_controller_spec.rb
291
+ - spec/models/scimitar/complex_types/address_spec.rb
292
+ - spec/models/scimitar/complex_types/email_spec.rb
270
293
  - spec/models/scimitar/lists/count_spec.rb
294
+ - spec/models/scimitar/lists/query_parser_spec.rb
295
+ - spec/models/scimitar/resource_type_spec.rb
296
+ - spec/models/scimitar/resources/base_spec.rb
271
297
  - spec/models/scimitar/resources/base_validation_spec.rb
272
298
  - spec/models/scimitar/resources/mixin_spec.rb
273
299
  - spec/models/scimitar/resources/user_spec.rb
274
- - spec/models/scimitar/resources/base_spec.rb
275
300
  - spec/models/scimitar/schema/attribute_spec.rb
301
+ - spec/models/scimitar/schema/base_spec.rb
276
302
  - spec/models/scimitar/schema/group_spec.rb
277
303
  - spec/models/scimitar/schema/user_spec.rb
278
- - spec/models/scimitar/schema/base_spec.rb
279
- - spec/models/scimitar/resource_type_spec.rb
280
- - spec/models/scimitar/complex_types/email_spec.rb
281
- - spec/models/scimitar/complex_types/address_spec.rb
282
- - spec/requests/controller_configuration_spec.rb
283
- - spec/requests/engine_spec.rb
284
304
  - spec/requests/active_record_backed_resources_controller_spec.rb
285
305
  - spec/requests/application_controller_spec.rb
306
+ - spec/requests/controller_configuration_spec.rb
307
+ - spec/requests/engine_spec.rb
308
+ - spec/spec_helper.rb
286
309
  - spec/spec_helper_spec.rb
287
310
  - spec/support/hash_with_indifferent_case_insensitive_access_spec.rb
288
- - spec/controllers/scimitar/schemas_controller_spec.rb
289
- - spec/controllers/scimitar/resource_types_controller_spec.rb
290
- - spec/controllers/scimitar/resources_controller_spec.rb
291
- - spec/controllers/scimitar/service_provider_configurations_controller_spec.rb
292
- - spec/controllers/scimitar/application_controller_spec.rb
293
- - spec/apps/dummy/app/models/mock_group.rb
294
- - spec/apps/dummy/app/models/mock_user.rb
295
- - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
296
- - spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
297
- - spec/apps/dummy/app/controllers/mock_groups_controller.rb
298
- - spec/apps/dummy/app/controllers/mock_users_controller.rb
299
- - spec/apps/dummy/config/routes.rb
300
- - spec/apps/dummy/config/environments/test.rb
301
- - spec/apps/dummy/config/environment.rb
302
- - spec/apps/dummy/config/application.rb
303
- - spec/apps/dummy/config/boot.rb
304
- - spec/apps/dummy/config/initializers/session_store.rb
305
- - spec/apps/dummy/config/initializers/cookies_serializer.rb
306
- - spec/apps/dummy/config/initializers/scimitar.rb
307
- - spec/apps/dummy/db/schema.rb
308
- - spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb
309
- - spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb
310
- - spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb