scimitar 1.6.0 → 1.7.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: 7bed3013773832fde9275ba69b622a8538beb2d8ff6b6ffe506e1c9453480ec5
4
- data.tar.gz: e51c811619169e64f3a373a56670514d37ac2823b7ed8b1bedd5c9b9c282576e
3
+ metadata.gz: 415331a6848887b5279a7f2c36ba5406989d490104a9f14735baf270bbb4f40a
4
+ data.tar.gz: 0ae6da1d6530f5fa4e5bce62f7e4188439281d768ada5f94534c19b3da8f8db9
5
5
  SHA512:
6
- metadata.gz: 697b3299edb4752baf38b68574191b72528a5e69467aa1a097c9b8c92928d57cff9d21afa50d7c66c15fc66220e52469f2b602ef3a5051489f734717f3489955
7
- data.tar.gz: 7d7a7bcb8fa0a9881ad7b77b6b81a4ec096e56300e8b5594fb0538adb76f7ee06b65fb56e319f1b7872738e4807d667e534f5c99b60d86b8bf5125e0d2972dc8
6
+ metadata.gz: a62b9ccb023fb73f16a6b98a6a457caa9e3b2fcd72d420b992c2798705d604c91a013e4606a7f9be6da3fa2ea4ce03a5b7292f181ef8ad3d794ec6e8beb9ef3b
7
+ data.tar.gz: 80ad36659de5ddd2e9b8b7f8fc3e74c0a663b0a00b01a812edf641352b9b9ee37e8cf010cc86233789a1488df34dd0c79c493aebadb3f06b315ae26a5c03e1ff
@@ -153,7 +153,13 @@ module Scimitar
153
153
  # Save a record, dealing with validation exceptions by raising SCIM
154
154
  # errors.
155
155
  #
156
- # +record+:: ActiveRecord subclass to save (via #save!).
156
+ # +record+:: ActiveRecord subclass to save.
157
+ #
158
+ # If you just let this superclass handle things, it'll call the standard
159
+ # +#save!+ method on the record. If you pass a block, then this block is
160
+ # invoked and passed the ActiveRecord model instance to be saved. You can
161
+ # then do things like calling a different method, using a service object
162
+ # of some kind, perform audit-related operations and so-on.
157
163
  #
158
164
  # The return value is not used internally, making life easier for
159
165
  # overriding subclasses to "do the right thing" / avoid mistakes (instead
@@ -161,10 +167,22 @@ module Scimitar
161
167
  # and relying upon this to generate correct response payloads - an early
162
168
  # version of the gem did this and it caused a confusing subclass bug).
163
169
  #
164
- def save!(record)
165
- record.save!
166
-
170
+ def save!(record, &block)
171
+ if block_given?
172
+ yield(record)
173
+ else
174
+ record.save!
175
+ end
167
176
  rescue ActiveRecord::RecordInvalid => exception
177
+ handle_invalid_record(exception.record)
178
+ end
179
+
180
+ # Deal with validation errors by responding with an appropriate SCIM
181
+ # error.
182
+ #
183
+ # +record+:: The record with validation errors.
184
+ #
185
+ def handle_invalid_record(record)
168
186
  joined_errors = record.errors.full_messages.join('; ')
169
187
 
170
188
  # https://tools.ietf.org/html/rfc7644#page-12
@@ -4,6 +4,11 @@ module Scimitar
4
4
  class SchemasController < ApplicationController
5
5
  def index
6
6
  schemas = Scimitar::Engine.schemas
7
+
8
+ schemas.each do |schema|
9
+ schema.meta.location = scim_schemas_url(name: schema.id)
10
+ end
11
+
7
12
  schemas_by_id = schemas.reduce({}) do |hash, schema|
8
13
  hash[schema.id] = schema
9
14
  hash
@@ -1,6 +1,6 @@
1
1
  module Scimitar
2
2
  module Errors
3
- def add_errors_from_hash(errors_hash, prefix: nil)
3
+ def add_errors_from_hash(errors_hash:, prefix: nil)
4
4
  errors_hash.each_pair do |key, value|
5
5
  new_key = prefix.nil? ? key : "#{prefix}.#{key}".to_sym
6
6
  if value.is_a?(Array)
@@ -139,13 +139,23 @@ module Scimitar
139
139
 
140
140
  def as_json(options = {})
141
141
  self.meta = Meta.new unless self.meta && self.meta.is_a?(Meta)
142
- meta.resourceType = self.class.resource_type_id
143
- original_hash = super(options).except('errors')
142
+ self.meta.resourceType = self.class.resource_type_id
143
+
144
+ non_returnable_attributes = self.class
145
+ .schemas
146
+ .flat_map(&:scim_attributes)
147
+ .filter_map { |attribute| attribute.name if attribute.returned == 'never' }
148
+
149
+ non_returnable_attributes << 'errors'
150
+
151
+ original_hash = super(options).except(*non_returnable_attributes)
144
152
  original_hash.merge!('schemas' => self.class.schemas.map(&:id))
153
+
145
154
  self.class.extended_schemas.each do |extension_schema|
146
155
  extension_attributes = extension_schema.scim_attributes.map(&:name)
147
156
  original_hash.merge!(extension_schema.id => original_hash.extract!(*extension_attributes))
148
157
  end
158
+
149
159
  original_hash
150
160
  end
151
161
 
@@ -88,19 +88,28 @@ module Scimitar
88
88
  end
89
89
  value.class.schema.valid?(value)
90
90
  return true if value.errors.empty?
91
- add_errors_from_hash(value.errors.to_hash, prefix: self.name)
91
+ add_errors_from_hash(errors_hash: value.errors.to_hash, prefix: self.name)
92
92
  false
93
93
  end
94
94
 
95
95
  def valid_simple_type?(value)
96
- valid = (type == 'string' && value.is_a?(String)) ||
97
- (type == 'boolean' && (value.is_a?(TrueClass) || value.is_a?(FalseClass))) ||
98
- (type == 'integer' && (value.is_a?(Integer))) ||
99
- (type == 'dateTime' && valid_date_time?(value))
100
- errors.add(self.name, "has the wrong type. It has to be a(n) #{self.type}.") unless valid
96
+ if multiValued
97
+ valid = value.is_a?(Array) && value.all? { |v| simple_type?(v) }
98
+ errors.add(self.name, "or one of its elements has the wrong type. It has to be an array of #{self.type}s.") unless valid
99
+ else
100
+ valid = simple_type?(value)
101
+ errors.add(self.name, "has the wrong type. It has to be a(n) #{self.type}.") unless valid
102
+ end
101
103
  valid
102
104
  end
103
105
 
106
+ def simple_type?(value)
107
+ (type == 'string' && value.is_a?(String)) ||
108
+ (type == 'boolean' && (value.is_a?(TrueClass) || value.is_a?(FalseClass))) ||
109
+ (type == 'integer' && (value.is_a?(Integer))) ||
110
+ (type == 'dateTime' && valid_date_time?(value))
111
+ end
112
+
104
113
  def valid_date_time?(value)
105
114
  !!Time.iso8601(value)
106
115
  rescue ArgumentError
@@ -13,7 +13,7 @@ module Scimitar
13
13
 
14
14
  # Converts the schema to its json representation that will be returned by /SCHEMAS end-point of a SCIM service provider.
15
15
  def as_json(options = {})
16
- @meta.location = Scimitar::Engine.routes.url_helpers.scim_schemas_path(name: id)
16
+ @meta.location ||= Scimitar::Engine.routes.url_helpers.scim_schemas_path(name: id)
17
17
  original = super
18
18
  original.merge('attributes' => original.delete('scim_attributes'))
19
19
  end
@@ -26,7 +26,9 @@ module Scimitar
26
26
  #
27
27
  def self.valid?(resource)
28
28
  cloned_scim_attributes.each do |scim_attribute|
29
- resource.add_errors_from_hash(scim_attribute.errors.to_hash) unless scim_attribute.valid?(resource.send(scim_attribute.name))
29
+ unless scim_attribute.valid?(resource.send(scim_attribute.name))
30
+ resource.add_errors_from_hash(errors_hash: scim_attribute.errors.to_hash)
31
+ end
30
32
  end
31
33
  end
32
34
 
@@ -38,9 +38,10 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
38
38
  Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
39
39
 
40
40
  # If you have filters you want to run for any Scimitar action/route, you
41
- # can define them here. For example, you might use a before-action to set
42
- # up some multi-tenancy related state, or skip Rails CSRF token
43
- # verification. For example:
41
+ # can define them here. You can also override any shared controller methods
42
+ # here. For example, you might use a before-action to set up some
43
+ # multi-tenancy related state, skip Rails CSRF token verification, or
44
+ # customise how Scimitar generates URLs:
44
45
  #
45
46
  # application_controller_mixin: Module.new do
46
47
  # def self.included(base)
@@ -54,6 +55,10 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
54
55
  # prepend_before_action :setup_some_kind_of_multi_tenancy_data
55
56
  # end
56
57
  # end
58
+ #
59
+ # def scim_schemas_url(options)
60
+ # super(custom_param: 'value', **options)
61
+ # end
57
62
  # end, # ...other configuration entries might follow...
58
63
 
59
64
  # If you want to support username/password authentication:
@@ -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.6.0'
6
+ VERSION = '1.7.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 = '2023-09-25'
11
+ DATE = '2023-11-15'
12
12
 
13
13
  end
@@ -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
@@ -1,5 +1,7 @@
1
1
  class MockUser < ActiveRecord::Base
2
2
 
3
+ self.primary_key = :primary_key
4
+
3
5
  # ===========================================================================
4
6
  # TEST ATTRIBUTES - see db/migrate/20210304014602_create_mock_users.rb etc.
5
7
  # ===========================================================================
@@ -8,6 +10,7 @@ class MockUser < ActiveRecord::Base
8
10
  primary_key
9
11
  scim_uid
10
12
  username
13
+ password
11
14
  first_name
12
15
  last_name
13
16
  work_email_address
@@ -44,6 +47,7 @@ class MockUser < ActiveRecord::Base
44
47
  id: :primary_key,
45
48
  externalId: :scim_uid,
46
49
  userName: :username,
50
+ password: :password,
47
51
  name: {
48
52
  givenName: :first_name,
49
53
  familyName: :last_name
@@ -26,6 +26,14 @@ Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
26
26
  before_action :test_hook
27
27
  end
28
28
  end
29
+
30
+ def scim_schemas_url(options)
31
+ super(test: 1, **options)
32
+ end
33
+
34
+ def scim_resource_type_url(options)
35
+ super(test: 1, **options)
36
+ end
29
37
  end
30
38
 
31
39
  })
@@ -21,6 +21,11 @@ Rails.application.routes.draw do
21
21
  #
22
22
  delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
23
23
 
24
+ # For testing blocks passed to ActiveRecordBackedResourcesController#save!
25
+ #
26
+ post 'CustomSaveUsers', to: 'custom_save_mock_users#create'
27
+ get 'CustomSaveUsers/:id', to: 'custom_save_mock_users#show'
28
+
24
29
  # For testing environment inside Scimitar::ApplicationController subclasses.
25
30
  #
26
31
  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
@@ -34,6 +34,7 @@ ActiveRecord::Schema.define(version: 2021_03_08_044214) do
34
34
  t.datetime "updated_at", precision: 6, null: false
35
35
  t.text "scim_uid"
36
36
  t.text "username"
37
+ t.text "password"
37
38
  t.text "first_name"
38
39
  t.text "last_name"
39
40
  t.text "work_email_address"
@@ -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')),
13
- Scimitar::Resources::Group.resource_type(scim_resource_type_url(name: 'Group'))
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))
14
14
  ].to_json
15
15
 
16
16
  response_hash = JSON.parse(response.body)
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Scimitar::SchemasController do
4
+ routes { Scimitar::Engine.routes }
4
5
 
5
6
  before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
6
7
 
@@ -26,6 +27,13 @@ RSpec.describe Scimitar::SchemasController do
26
27
  expect(parsed_body['name']).to eql('User')
27
28
  end
28
29
 
30
+ it 'includes the controller customized 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
+
29
37
  it 'returns only the Group schema when its id is provided' do
30
38
  get :index, params: { name: Scimitar::Schema::Group.id, format: :scim }
31
39
  expect(response).to be_ok
@@ -405,7 +405,7 @@ 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.id])
408
+ expect(query.pluck(:primary_key)).to eql([user_1.primary_key])
409
409
 
410
410
  @instance.parse('name.givenName sw J') # First name starts with 'J'
411
411
  query = @instance.to_activerecord_query(MockUser.all)
@@ -14,7 +14,10 @@ RSpec.describe Scimitar::Resources::Base do
14
14
  ),
15
15
  Scimitar::Schema::Attribute.new(
16
16
  name: 'names', multiValued: true, complexType: Scimitar::ComplexTypes::Name, required: false
17
- )
17
+ ),
18
+ Scimitar::Schema::Attribute.new(
19
+ name: 'privateName', complexType: Scimitar::ComplexTypes::Name, required: false, returned: false
20
+ ),
18
21
  ]
19
22
  end
20
23
  end
@@ -30,6 +33,10 @@ RSpec.describe Scimitar::Resources::Base do
30
33
  name: {
31
34
  givenName: 'John',
32
35
  familyName: 'Smith'
36
+ },
37
+ privateName: {
38
+ givenName: 'Alt John',
39
+ familyName: 'Alt Smith'
33
40
  }
34
41
  }
35
42
 
@@ -39,6 +46,9 @@ RSpec.describe Scimitar::Resources::Base do
39
46
  expect(resource.name.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
40
47
  expect(resource.name.givenName).to eql('John')
41
48
  expect(resource.name.familyName).to eql('Smith')
49
+ expect(resource.privateName.is_a?(Scimitar::ComplexTypes::Name)).to be(true)
50
+ expect(resource.privateName.givenName).to eql('Alt John')
51
+ expect(resource.privateName.familyName).to eql('Alt Smith')
42
52
  end
43
53
 
44
54
  it 'which builds an array of nested resources' do
@@ -101,14 +111,38 @@ RSpec.describe Scimitar::Resources::Base do
101
111
  context '#as_json' do
102
112
  it 'renders the json with the resourceType' do
103
113
  resource = CustomResourse.new(name: {
104
- givenName: 'John',
114
+ givenName: 'John',
105
115
  familyName: 'Smith'
106
116
  })
107
117
 
108
118
  result = resource.as_json
109
- expect(result['schemas']).to eql(['custom-id'])
119
+
120
+ expect(result['schemas'] ).to eql(['custom-id'])
121
+ expect(result['meta']['resourceType']).to eql('CustomResourse')
122
+ expect(result['errors'] ).to be_nil
123
+ end
124
+
125
+ it 'excludes attributes that are flagged as do-not-return' do
126
+ resource = CustomResourse.new(
127
+ name: {
128
+ givenName: 'John',
129
+ familyName: 'Smith'
130
+ },
131
+ privateName: {
132
+ givenName: 'Alt John',
133
+ familyName: 'Alt Smith'
134
+ }
135
+ )
136
+
137
+ result = resource.as_json
138
+
139
+ expect(result['schemas'] ).to eql(['custom-id'])
110
140
  expect(result['meta']['resourceType']).to eql('CustomResourse')
111
- expect(result['errors']).to be_nil
141
+ expect(result['errors'] ).to be_nil
142
+ expect(result['name'] ).to be_present
143
+ expect(result['name']['givenName'] ).to eql('John')
144
+ expect(result['name']['familyName'] ).to eql('Smith')
145
+ expect(result['privateName'] ).to be_present
112
146
  end
113
147
  end # "context '#as_json' do"
114
148
 
@@ -160,13 +160,14 @@ RSpec.describe Scimitar::Resources::Mixin do
160
160
 
161
161
  context '#to_scim' do
162
162
  context 'with a UUID, renamed primary key column' do
163
- it 'compiles instance attribute values into a SCIM representation' do
163
+ it 'compiles instance attribute values into a SCIM representation, but omits do-not-return fields' do
164
164
  uuid = SecureRandom.uuid
165
165
 
166
166
  instance = MockUser.new
167
167
  instance.primary_key = uuid
168
168
  instance.scim_uid = 'AA02984'
169
169
  instance.username = 'foo'
170
+ instance.password = 'correcthorsebatterystaple'
170
171
  instance.first_name = 'Foo'
171
172
  instance.last_name = 'Bar'
172
173
  instance.work_email_address = 'foo.bar@test.com'
@@ -404,6 +405,7 @@ RSpec.describe Scimitar::Resources::Mixin do
404
405
  it 'ignoring read-only lists' do
405
406
  hash = {
406
407
  'userName' => 'foo',
408
+ 'password' => 'staplebatteryhorsecorrect',
407
409
  'name' => {'givenName' => 'Foo', 'familyName' => 'Bar'},
408
410
  'active' => true,
409
411
  'emails' => [{'type' => 'work', 'primary' => true, 'value' => 'foo.bar@test.com'}],
@@ -428,6 +430,7 @@ RSpec.describe Scimitar::Resources::Mixin do
428
430
 
429
431
  expect(instance.scim_uid ).to eql('AA02984')
430
432
  expect(instance.username ).to eql('foo')
433
+ expect(instance.password ).to eql('staplebatteryhorsecorrect')
431
434
  expect(instance.first_name ).to eql('Foo')
432
435
  expect(instance.last_name ).to eql('Bar')
433
436
  expect(instance.work_email_address).to eql('foo.bar@test.com')
@@ -42,25 +42,25 @@ 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(errors_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(errors_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
55
55
 
56
56
  it 'adds the error with prefix when the value is a string' do
57
- user.add_errors_from_hash({key: 'some error'}, prefix: :pre)
57
+ user.add_errors_from_hash(errors_hash: {key: 'some error'}, prefix: :pre)
58
58
  expect(user.errors.messages.to_h).to eql({:'pre.key' => ['some error']})
59
59
  expect(user.errors.full_messages).to eql(['Pre key some error'])
60
60
  end
61
61
 
62
62
  it 'adds the error wity prefix when the value is an array' do
63
- user.add_errors_from_hash({key: ['error1', 'error2']}, prefix: :pre)
63
+ user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']}, prefix: :pre)
64
64
  expect(user.errors.messages.to_h).to eql({:'pre.key' => ['error1', 'error2']})
65
65
  expect(user.errors.full_messages).to eql(['Pre key error1', 'Pre key error2'])
66
66
  end
@@ -46,6 +46,28 @@ RSpec.describe Scimitar::Schema::Attribute do
46
46
  expect(attribute.errors.messages.to_h).to eql({userName: ['has the wrong type. It has to be a(n) string.']})
47
47
  end
48
48
 
49
+ it 'is valid if multi-valued and type is string and given value is an array of strings' do
50
+ attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
51
+ expect(attribute.valid?(['something', 'something else'])).to be(true)
52
+ end
53
+
54
+ it 'is valid if multi-valued and type is string and given value is an empty array' do
55
+ attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
56
+ expect(attribute.valid?([])).to be(true)
57
+ end
58
+
59
+ it 'is invalid if multi-valued and type is string and given value is not an array' do
60
+ attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
61
+ expect(attribute.valid?('something')).to be(false)
62
+ expect(attribute.errors.messages.to_h).to eql({scopes: ['or one of its elements has the wrong type. It has to be an array of strings.']})
63
+ end
64
+
65
+ it 'is invalid if multi-valued and type is string and given value is an array containing another type' do
66
+ attribute = described_class.new(name: 'scopes', multiValued: true, type: 'string')
67
+ expect(attribute.valid?(['something', 123])).to be(false)
68
+ expect(attribute.errors.messages.to_h).to eql({scopes: ['or one of its elements has the wrong type. It has to be an array of strings.']})
69
+ end
70
+
49
71
  it 'is valid if type is boolean and given value is boolean' do
50
72
  expect(described_class.new(name: 'name', type: 'boolean').valid?(false)).to be(true)
51
73
  expect(described_class.new(name: 'name', type: 'boolean').valid?(true)).to be(true)
@@ -5,8 +5,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
5
5
  before :each do
6
6
  allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
7
7
 
8
- lmt = Time.parse("2023-01-09 14:25:00 +1300")
9
-
10
8
  # If a sort order is unspecified, the controller defaults to ID ascending.
11
9
  # With UUID based IDs, testing life is made easier by ensuring that the
12
10
  # creation order matches an ascending UUID sort order (which is what would
@@ -282,7 +280,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
282
280
  it 'with minimal parameters' do
283
281
  mock_before = MockUser.all.to_a
284
282
 
285
- attributes = { userName: '4' } # Minimum required by schema
283
+ attributes = { userName: '4' } # Minimum required by schema
286
284
  attributes = spec_helper_hupcase(attributes) if force_upper_case
287
285
 
288
286
  expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
@@ -337,7 +335,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
337
335
  expect(response.status).to eql(201)
338
336
  result = JSON.parse(response.body)
339
337
 
340
- expect(result['id']).to eql(new_mock.primary_key.to_s)
338
+ expect(result['id']).to eql(new_mock.id.to_s)
341
339
  expect(result['meta']['resourceType']).to eql('User')
342
340
  expect(new_mock.username).to eql('4')
343
341
  expect(new_mock.first_name).to eql('Given')
@@ -399,6 +397,22 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
399
397
  expect(result['scimType']).to eql('invalidValue')
400
398
  expect(result['detail']).to include('is reserved')
401
399
  end
400
+
401
+ it 'invokes a block if given one' do
402
+ mock_before = MockUser.all.to_a
403
+ attributes = { userName: '5' } # Minimum required by schema
404
+
405
+ expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
406
+ expect {
407
+ post "/CustomSaveUsers", params: attributes.merge(format: :scim)
408
+ }.to change { MockUser.count }.by(1)
409
+
410
+ mock_after = MockUser.all.to_a
411
+ new_mock = (mock_after - mock_before).first
412
+
413
+ expect(response.status).to eql(201)
414
+ expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
415
+ end
402
416
  end # "context '#create' do"
403
417
 
404
418
  # ===========================================================================
@@ -18,7 +18,7 @@ RSpec.describe Scimitar::ApplicationController do
18
18
  parsed_body = JSON.parse(response.body)
19
19
  expect(parsed_body['request']['is_scim' ]).to eql(true)
20
20
  expect(parsed_body['request']['format' ]).to eql('application/scim+json')
21
- expect(parsed_body['request']['content_type']).to eql('application/scim+json')
21
+ expect(parsed_body['request']['content_type']).to eql('application/scim+json') # Filled in by ApplicationController#require_scim
22
22
  end
23
23
 
24
24
  it 'renders 400 if given bad JSON' do
@@ -26,7 +26,6 @@ RSpec.describe Scimitar::ApplicationController do
26
26
 
27
27
  expect(response).to have_http_status(:bad_request)
28
28
  expect(JSON.parse(response.body)['detail']).to start_with('Invalid JSON - ')
29
- expect(JSON.parse(response.body)['detail']).to include("'not-json-12345'")
30
29
  end
31
30
 
32
31
  it 'translates Content-Type to Rails request format' do
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.6.0
4
+ version: 1.7.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: 2023-09-25 00:00:00.000000000 Z
12
+ date: 2023-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -196,6 +196,7 @@ files:
196
196
  - lib/scimitar/version.rb
197
197
  - spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
198
198
  - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
199
+ - spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
199
200
  - spec/apps/dummy/app/controllers/mock_groups_controller.rb
200
201
  - spec/apps/dummy/app/controllers/mock_users_controller.rb
201
202
  - spec/apps/dummy/app/models/mock_group.rb
@@ -260,13 +261,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
260
261
  - !ruby/object:Gem::Version
261
262
  version: '0'
262
263
  requirements: []
263
- rubygems_version: 3.4.4
264
+ rubygems_version: 3.4.10
264
265
  signing_key:
265
266
  specification_version: 4
266
267
  summary: SCIM v2 for Rails
267
268
  test_files:
268
269
  - spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb
269
270
  - spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb
271
+ - spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb
270
272
  - spec/apps/dummy/app/controllers/mock_groups_controller.rb
271
273
  - spec/apps/dummy/app/controllers/mock_users_controller.rb
272
274
  - spec/apps/dummy/app/models/mock_group.rb