scimitar 1.8.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +20 -94
  3. data/app/controllers/scimitar/application_controller.rb +13 -41
  4. data/app/controllers/scimitar/schemas_controller.rb +0 -5
  5. data/app/models/scimitar/complex_types/address.rb +6 -0
  6. data/app/models/scimitar/engine_configuration.rb +5 -13
  7. data/app/models/scimitar/error_response.rb +0 -12
  8. data/app/models/scimitar/lists/query_parser.rb +10 -25
  9. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  10. data/app/models/scimitar/resources/base.rb +4 -17
  11. data/app/models/scimitar/resources/mixin.rb +42 -539
  12. data/app/models/scimitar/schema/address.rb +0 -1
  13. data/app/models/scimitar/schema/attribute.rb +5 -14
  14. data/app/models/scimitar/schema/base.rb +1 -1
  15. data/app/models/scimitar/schema/vdtp.rb +1 -1
  16. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  17. data/config/initializers/scimitar.rb +3 -28
  18. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
  19. data/lib/scimitar/version.rb +2 -2
  20. data/lib/scimitar.rb +2 -7
  21. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  22. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  23. data/spec/apps/dummy/app/models/mock_user.rb +8 -36
  24. data/spec/apps/dummy/config/application.rb +1 -0
  25. data/spec/apps/dummy/config/environments/test.rb +28 -5
  26. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
  27. data/spec/apps/dummy/config/routes.rb +7 -28
  28. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
  29. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  30. data/spec/apps/dummy/db/schema.rb +4 -11
  31. data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
  32. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  33. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
  34. data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
  35. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  36. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
  37. data/spec/models/scimitar/resources/base_spec.rb +70 -216
  38. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  39. data/spec/models/scimitar/resources/mixin_spec.rb +129 -1447
  40. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  41. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  42. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  43. data/spec/requests/active_record_backed_resources_controller_spec.rb +68 -787
  44. data/spec/requests/application_controller_spec.rb +3 -16
  45. data/spec/spec_helper.rb +0 -8
  46. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
  47. metadata +14 -25
  48. data/LICENSE.txt +0 -21
  49. data/README.md +0 -710
  50. data/lib/scimitar/support/utilities.rb +0 -51
  51. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
  52. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
  53. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
  54. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -1,13 +1,8 @@
1
1
  class CreateJoinTableMockGroupsMockUsers < ActiveRecord::Migration[6.1]
2
2
  def change
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
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,26 +24,19 @@ 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.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"
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", primary_key: "primary_key", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
33
- t.datetime "created_at", null: false
34
- t.datetime "updated_at", null: false
32
+ create_table "mock_users", force: :cascade do |t|
35
33
  t.text "scim_uid"
36
34
  t.text "username"
37
- t.text "password"
38
35
  t.text "first_name"
39
36
  t.text "last_name"
40
37
  t.text "work_email_address"
41
38
  t.text "home_email_address"
42
39
  t.text "work_phone_number"
43
- t.text "organization"
44
- t.text "department"
45
40
  end
46
41
 
47
- add_foreign_key "mock_groups_users", "mock_groups"
48
- add_foreign_key "mock_groups_users", "mock_users", primary_key: "primary_key"
49
42
  end
@@ -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['WWW-Authenticate']).to eql('Basic')
27
+ expect(response.headers['WWW_AUTHENTICATE']).to eql('Basic')
28
28
  end
29
29
 
30
30
  it 'renders failure with bad password' do
@@ -84,61 +84,7 @@ 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['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')
87
+ expect(response.headers['WWW_AUTHENTICATE']).to eql('Bearer')
142
88
  end
143
89
 
144
90
  it 'renders failure with bad token' do
@@ -223,74 +169,5 @@ RSpec.describe Scimitar::ApplicationController do
223
169
  expect(parsed_body).to include('status' => '500')
224
170
  expect(parsed_body).to include('detail' => 'Bang')
225
171
  end
226
-
227
- context 'with an exception reporter' do
228
- around :each do | example |
229
- original_configuration = Scimitar.engine_configuration.exception_reporter
230
- Scimitar.engine_configuration.exception_reporter = Proc.new do | exception |
231
- @exception = exception
232
- end
233
- example.run()
234
- ensure
235
- Scimitar.engine_configuration.exception_reporter = original_configuration
236
- end
237
-
238
- context 'and "internal server error"' do
239
- it 'is invoked' do
240
- get :index, params: { format: :scim }
241
-
242
- expect(@exception).to be_a(RuntimeError)
243
- expect(@exception.message).to eql('Bang')
244
- end
245
- end
246
-
247
- context 'and "not found"' do
248
- controller do
249
- def index
250
- handle_resource_not_found(ActiveRecord::RecordNotFound.new(42))
251
- end
252
- end
253
-
254
- it 'is invoked' do
255
- get :index, params: { format: :scim }
256
-
257
- expect(@exception).to be_a(ActiveRecord::RecordNotFound)
258
- expect(@exception.message).to eql('42')
259
- end
260
- end
261
-
262
- context 'and bad JSON' do
263
- controller do
264
- def index
265
- begin
266
- raise 'Hello'
267
- rescue
268
- raise ActionDispatch::Http::Parameters::ParseError
269
- end
270
- end
271
- end
272
-
273
- it 'is invoked' do
274
- get :index, params: { format: :scim }
275
-
276
- expect(@exception).to be_a(ActionDispatch::Http::Parameters::ParseError)
277
- expect(@exception.message).to eql('Hello')
278
- end
279
- end
280
-
281
- context 'and a bad content type' do
282
- controller do
283
- def index; end
284
- end
285
-
286
- it 'is invoked' do
287
- request.headers['Content-Type'] = 'text/plain'
288
- get :index
289
-
290
- expect(@exception).to be_a(Scimitar::ErrorResponse)
291
- expect(@exception.message).to eql('Only application/scim+json type is accepted.')
292
- end
293
- end
294
- end # "context 'exception reporter' do"
295
- end # "context 'error handling' do"
172
+ end
296
173
  end
@@ -9,8 +9,8 @@ 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 = [ Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User', test: 1)),
13
- Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group', test: 1))
12
+ expected_response = [ Scimitar::Resources::User.resource_type(scim_resource_type_url(name: 'User')),
13
+ Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group'))
14
14
  ].to_json
15
15
 
16
16
  response_hash = JSON.parse(response.body)
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Scimitar::SchemasController do
4
- routes { Scimitar::Engine.routes }
5
4
 
6
5
  before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
7
6
 
@@ -15,9 +14,9 @@ RSpec.describe Scimitar::SchemasController do
15
14
  get :index, params: { format: :scim }
16
15
  expect(response).to be_ok
17
16
  parsed_body = JSON.parse(response.body)
18
- expect(parsed_body.length).to eql(3)
17
+ expect(parsed_body.length).to eql(2)
19
18
  schema_names = parsed_body.map {|schema| schema['name']}
20
- expect(schema_names).to match_array(['User', 'ExtendedUser', 'Group'])
19
+ expect(schema_names).to match_array(['User', 'Group'])
21
20
  end
22
21
 
23
22
  it 'returns only the User schema when its id is provided' do
@@ -27,13 +26,6 @@ RSpec.describe Scimitar::SchemasController do
27
26
  expect(parsed_body['name']).to eql('User')
28
27
  end
29
28
 
30
- it 'includes the controller customised schema location' do
31
- get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
32
- expect(response).to be_ok
33
- parsed_body = JSON.parse(response.body)
34
- expect(parsed_body.dig('meta', 'location')).to eq scim_schemas_url(name: Scimitar::Schema::User.id, test: 1)
35
- end
36
-
37
29
  it 'returns only the Group schema when its id is provided' do
38
30
  get :index, params: { name: Scimitar::Schema::Group.id, format: :scim }
39
31
  expect(response).to be_ok
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe Scimitar::ComplexTypes::Address do
4
4
  context '#as_json' do
5
- it 'assumes no defaults' do
6
- expect(described_class.new.as_json).to eq({})
5
+ it 'assumes a type of "work" as a default' do
6
+ expect(described_class.new.as_json).to eq('type' => 'work')
7
7
  end
8
8
 
9
9
  it 'allows a custom address type' do
@@ -11,8 +11,9 @@ RSpec.describe Scimitar::ComplexTypes::Address do
11
11
  end
12
12
 
13
13
  it 'shows the set address' do
14
- expect(described_class.new(country: 'NZ').as_json).to eq('country' => 'NZ')
14
+ expect(described_class.new(country: 'NZ').as_json).to eq('type' => 'work', 'country' => 'NZ')
15
15
  end
16
16
  end
17
17
 
18
18
  end
19
+
@@ -18,4 +18,6 @@ RSpec.describe Scimitar::ComplexTypes::Email do
18
18
  expect(described_class.new(value: 'a@b.c').as_json).to eq('value' => 'a@b.c')
19
19
  end
20
20
  end
21
+
21
22
  end
23
+
@@ -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(:primary_key)).to eql([user_1.primary_key])
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(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:primary_key)).to eql([user_2.primary_key])
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(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
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(:primary_key)).to match_array([user_1.primary_key])
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
@@ -481,66 +481,6 @@ RSpec.describe Scimitar::Lists::QueryParser do
481
481
  end
482
482
  end # "context 'when instructed to ignore an attribute' do"
483
483
 
484
- context 'when an arel column is mapped' do
485
- let(:scope_with_groups) { MockUser.left_joins(:mock_groups) }
486
-
487
- context 'with binary operators' do
488
- it 'reads across all using OR' do
489
- @instance.parse('groups eq "12345"')
490
- query = @instance.to_activerecord_query(scope_with_groups)
491
-
492
- expect(query.to_sql).to eql(<<~SQL.squish)
493
- SELECT "mock_users".*
494
- FROM "mock_users"
495
- LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
496
- LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
497
- WHERE "mock_groups"."id" ILIKE 12345
498
- SQL
499
- end
500
-
501
- it 'works with other query elements using correct precedence' do
502
- @instance.parse('groups eq "12345" and emails eq "any@test.com"')
503
- query = @instance.to_activerecord_query(scope_with_groups)
504
-
505
- expect(query.to_sql).to eql(<<~SQL.squish)
506
- SELECT "mock_users".*
507
- FROM "mock_users"
508
- LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
509
- LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
510
- WHERE "mock_groups"."id" ILIKE 12345 AND ("mock_users"."work_email_address" ILIKE 'any@test.com' OR "mock_users"."home_email_address" ILIKE 'any@test.com')
511
- SQL
512
- end
513
- end # "context 'with binary operators' do"
514
-
515
- context 'with unary operators' do
516
- it 'reads across all using OR' do
517
- @instance.parse('groups pr')
518
- query = @instance.to_activerecord_query(scope_with_groups)
519
-
520
- expect(query.to_sql).to eql(<<~SQL.squish)
521
- SELECT "mock_users".*
522
- FROM "mock_users"
523
- LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
524
- LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
525
- WHERE ("mock_groups"."id" != NULL AND "mock_groups"."id" IS NOT NULL)
526
- SQL
527
- end
528
-
529
- it 'works with other query elements using correct precedence' do
530
- @instance.parse('name.familyName eq "John" and groups pr')
531
- query = @instance.to_activerecord_query(scope_with_groups)
532
-
533
- expect(query.to_sql).to eql(<<~SQL.squish)
534
- SELECT "mock_users".*
535
- FROM "mock_users"
536
- LEFT OUTER JOIN "mock_groups_users" ON "mock_groups_users"."mock_user_id" = "mock_users"."primary_key"
537
- LEFT OUTER JOIN "mock_groups" ON "mock_groups"."id" = "mock_groups_users"."mock_group_id"
538
- WHERE "mock_users"."last_name" ILIKE 'John' AND ("mock_groups"."id" != NULL AND "mock_groups"."id" IS NOT NULL)
539
- SQL
540
- end
541
- end # "context 'with unary operators' do
542
- end # "context 'when an arel column is mapped' do"
543
-
544
484
  context 'with complex cases' do
545
485
  context 'using AND' do
546
486
  it 'generates expected SQL' do
@@ -559,7 +499,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
559
499
  query = @instance.to_activerecord_query(MockUser.all)
560
500
 
561
501
  expect(query.count).to eql(1)
562
- expect(query.pluck(:primary_key)).to match_array([user_2.primary_key])
502
+ expect(query.pluck(:id)).to match_array([user_2.id])
563
503
  end
564
504
  end # "context 'simple AND' do"
565
505
 
@@ -580,7 +520,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
580
520
  query = @instance.to_activerecord_query(MockUser.all)
581
521
 
582
522
  expect(query.count).to eql(2)
583
- expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key])
523
+ expect(query.pluck(:id)).to match_array([user_1.id, user_2.id])
584
524
  end
585
525
  end # "context 'simple OR' do"
586
526
 
@@ -592,13 +532,6 @@ RSpec.describe Scimitar::Lists::QueryParser do
592
532
  expect(query.to_sql).to eql(%q{SELECT "mock_users".* FROM "mock_users" WHERE "mock_users"."first_name" ILIKE 'Jane' AND ("mock_users"."last_name" ILIKE '%avi%' OR "mock_users"."last_name" ILIKE '%ith')})
593
533
  end
594
534
 
595
- it 'combined parentheses generates expected SQL' do
596
- @instance.parse('(name.givenName eq "Jane" OR name.givenName eq "Jaden") and (name.familyName co "avi" or name.familyName ew "ith")')
597
- query = @instance.to_activerecord_query(MockUser.all)
598
-
599
- expect(query.to_sql).to eql(%q{SELECT "mock_users".* FROM "mock_users" WHERE ("mock_users"."first_name" ILIKE 'Jane' OR "mock_users"."first_name" ILIKE 'Jaden') AND ("mock_users"."last_name" ILIKE '%avi%' OR "mock_users"."last_name" ILIKE '%ith')})
600
- end
601
-
602
535
  it 'finds expected items' do
603
536
  user_1 = MockUser.create(username: '1', first_name: 'Jane', last_name: 'Davis') # Match
604
537
  user_2 = MockUser.create(username: '2', first_name: 'Jane', last_name: 'Smith') # Match
@@ -613,7 +546,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
613
546
  query = @instance.to_activerecord_query(MockUser.all)
614
547
 
615
548
  expect(query.count).to eql(3)
616
- expect(query.pluck(:primary_key)).to match_array([user_1.primary_key, user_2.primary_key, user_3.primary_key])
549
+ expect(query.pluck(:id)).to match_array([user_1.id, user_2.id, user_3.id])
617
550
  end
618
551
  end # "context 'combined AND and OR' do"
619
552
 
@@ -657,7 +590,7 @@ RSpec.describe Scimitar::Lists::QueryParser do
657
590
  end
658
591
 
659
592
  it 'complains if there is no column mapping available' do
660
- expect { @instance.send(:activerecord_columns, 'userName') }.to raise_error(Scimitar::FilterError)
593
+ expect { @instance.send(:activerecord_columns, 'externalId') }.to raise_error(Scimitar::FilterError)
661
594
  end
662
595
 
663
596
  it 'complains about malformed declarations' do