scimitar 1.11.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +23 -98
  3. data/app/controllers/scimitar/application_controller.rb +17 -44
  4. data/app/controllers/scimitar/resource_types_controller.rb +3 -7
  5. data/app/controllers/scimitar/resources_controller.rb +2 -0
  6. data/app/controllers/scimitar/schemas_controller.rb +3 -366
  7. data/app/controllers/scimitar/service_provider_configurations_controller.rb +1 -0
  8. data/app/models/scimitar/complex_types/address.rb +6 -0
  9. data/app/models/scimitar/engine_configuration.rb +5 -15
  10. data/app/models/scimitar/error_response.rb +0 -12
  11. data/app/models/scimitar/lists/query_parser.rb +13 -113
  12. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  13. data/app/models/scimitar/resources/base.rb +9 -53
  14. data/app/models/scimitar/resources/mixin.rb +59 -646
  15. data/app/models/scimitar/schema/address.rb +0 -1
  16. data/app/models/scimitar/schema/attribute.rb +5 -14
  17. data/app/models/scimitar/schema/base.rb +1 -1
  18. data/app/models/scimitar/schema/name.rb +2 -2
  19. data/app/models/scimitar/schema/user.rb +10 -10
  20. data/app/models/scimitar/schema/vdtp.rb +1 -1
  21. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  22. data/config/initializers/scimitar.rb +3 -69
  23. data/lib/scimitar/engine.rb +12 -57
  24. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
  25. data/lib/scimitar/version.rb +2 -2
  26. data/lib/scimitar.rb +2 -7
  27. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  28. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  29. data/spec/apps/dummy/app/models/mock_user.rb +9 -52
  30. data/spec/apps/dummy/config/application.rb +1 -0
  31. data/spec/apps/dummy/config/environments/test.rb +28 -5
  32. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -90
  33. data/spec/apps/dummy/config/routes.rb +7 -28
  34. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -11
  35. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  36. data/spec/apps/dummy/db/schema.rb +4 -12
  37. data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
  38. data/spec/controllers/scimitar/resource_types_controller_spec.rb +4 -8
  39. data/spec/controllers/scimitar/schemas_controller_spec.rb +48 -344
  40. data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
  41. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  42. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -146
  43. data/spec/models/scimitar/resources/base_spec.rb +71 -217
  44. data/spec/models/scimitar/resources/base_validation_spec.rb +5 -43
  45. data/spec/models/scimitar/resources/mixin_spec.rb +129 -1508
  46. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  47. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  48. data/spec/models/scimitar/schema/user_spec.rb +2 -12
  49. data/spec/requests/active_record_backed_resources_controller_spec.rb +66 -1016
  50. data/spec/requests/application_controller_spec.rb +3 -16
  51. data/spec/requests/engine_spec.rb +0 -75
  52. data/spec/spec_helper.rb +1 -9
  53. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
  54. metadata +26 -37
  55. data/LICENSE.txt +0 -21
  56. data/README.md +0 -717
  57. data/lib/scimitar/support/utilities.rb +0 -111
  58. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
  59. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
  60. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
  61. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -10,7 +10,6 @@ module Scimitar
10
10
  def self.scim_attributes
11
11
  @scim_attributes ||= [
12
12
  Attribute.new(name: 'type', type: 'string'),
13
- Attribute.new(name: 'primary', type: 'boolean'),
14
13
  Attribute.new(name: 'formatted', type: 'string'),
15
14
  Attribute.new(name: 'streetAddress', type: 'string'),
16
15
  Attribute.new(name: 'locality', type: 'string'),
@@ -93,23 +93,14 @@ module Scimitar
93
93
  end
94
94
 
95
95
  def valid_simple_type?(value)
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
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
103
101
  valid
104
102
  end
105
103
 
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
-
113
104
  def valid_date_time?(value)
114
105
  !!Time.iso8601(value)
115
106
  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
@@ -6,8 +6,8 @@ module Scimitar
6
6
 
7
7
  def self.scim_attributes
8
8
  @scim_attributes ||= [
9
- Attribute.new(name: 'familyName', type: 'string'),
10
- Attribute.new(name: 'givenName', type: 'string'),
9
+ Attribute.new(name: 'familyName', type: 'string', required: true),
10
+ Attribute.new(name: 'givenName', type: 'string', required: true),
11
11
  Attribute.new(name: 'middleName', type: 'string'),
12
12
  Attribute.new(name: 'formatted', type: 'string'),
13
13
  Attribute.new(name: 'honorificPrefix', type: 'string'),
@@ -20,7 +20,7 @@ module Scimitar
20
20
  [
21
21
  Attribute.new(name: 'userName', type: 'string', uniqueness: 'server', required: true),
22
22
 
23
- Attribute.new(name: 'name', complexType: Scimitar::ComplexTypes::Name),
23
+ Attribute.new(name: 'name', complexType: Scimitar::ComplexTypes::Name),
24
24
 
25
25
  Attribute.new(name: 'displayName', type: 'string'),
26
26
  Attribute.new(name: 'nickName', type: 'string'),
@@ -35,15 +35,15 @@ module Scimitar
35
35
 
36
36
  Attribute.new(name: 'password', type: 'string', mutability: 'writeOnly', returned: 'never'),
37
37
 
38
- Attribute.new(name: 'emails', multiValued: true, complexType: Scimitar::ComplexTypes::Email),
39
- Attribute.new(name: 'phoneNumbers', multiValued: true, complexType: Scimitar::ComplexTypes::PhoneNumber),
40
- Attribute.new(name: 'ims', multiValued: true, complexType: Scimitar::ComplexTypes::Ims),
41
- Attribute.new(name: 'photos', multiValued: true, complexType: Scimitar::ComplexTypes::Photo),
42
- Attribute.new(name: 'addresses', multiValued: true, complexType: Scimitar::ComplexTypes::Address),
43
- Attribute.new(name: 'groups', multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: 'readOnly'),
44
- Attribute.new(name: 'entitlements', multiValued: true, complexType: Scimitar::ComplexTypes::Entitlement),
45
- Attribute.new(name: 'roles', multiValued: true, complexType: Scimitar::ComplexTypes::Role),
46
- Attribute.new(name: 'x509Certificates', multiValued: true, complexType: Scimitar::ComplexTypes::X509Certificate),
38
+ Attribute.new(name: 'emails', multiValued: true, complexType: Scimitar::ComplexTypes::Email),
39
+ Attribute.new(name: 'phoneNumbers', multiValued: true, complexType: Scimitar::ComplexTypes::PhoneNumber),
40
+ Attribute.new(name: 'ims', multiValued: true, complexType: Scimitar::ComplexTypes::Ims),
41
+ Attribute.new(name: 'photos', multiValued: true, complexType: Scimitar::ComplexTypes::Photo),
42
+ Attribute.new(name: 'addresses', multiValued: true, complexType: Scimitar::ComplexTypes::Address),
43
+ Attribute.new(name: 'groups', multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: 'readOnly'),
44
+ Attribute.new(name: 'entitlements', multiValued: true, complexType: Scimitar::ComplexTypes::Entitlement),
45
+ Attribute.new(name: 'roles', multiValued: true, complexType: Scimitar::ComplexTypes::Role),
46
+ Attribute.new(name: 'x509Certificates', multiValued: true, complexType: Scimitar::ComplexTypes::X509Certificate),
47
47
  ]
48
48
  end
49
49
 
@@ -7,7 +7,7 @@ module Scimitar
7
7
  class Vdtp < Base
8
8
  def self.scim_attributes
9
9
  @scim_attributes ||= [
10
- Attribute.new(name: 'value', type: 'string', required: Scimitar.engine_configuration.optional_value_fields_required),
10
+ Attribute.new(name: 'value', type: 'string', required: true),
11
11
  Attribute.new(name: 'display', type: 'string', mutability: 'readOnly'),
12
12
  Attribute.new(name: 'type', type: 'string'),
13
13
  Attribute.new(name: 'primary', type: 'boolean'),
@@ -9,22 +9,11 @@ module Scimitar
9
9
  class ServiceProviderConfiguration
10
10
  include ActiveModel::Model
11
11
 
12
- attr_accessor(
13
- :uses_defaults,
14
- :patch,
15
- :bulk,
16
- :filter,
17
- :changePassword,
18
- :sort,
19
- :etag,
20
- :authenticationSchemes,
21
- :schemas,
22
- :meta,
23
- )
12
+ attr_accessor :patch, :bulk, :filter, :changePassword,
13
+ :sort, :etag, :authenticationSchemes,
14
+ :schemas, :meta
24
15
 
25
16
  def initialize(attributes = {})
26
- @uses_defaults = attributes.empty?
27
-
28
17
  defaults = {
29
18
  bulk: Supportable.unsupported,
30
19
  changePassword: Supportable.unsupported,
@@ -38,10 +38,9 @@ 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. 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:
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:
45
44
  #
46
45
  # application_controller_mixin: Module.new do
47
46
  # def self.included(base)
@@ -55,10 +54,6 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
55
54
  # prepend_before_action :setup_some_kind_of_multi_tenancy_data
56
55
  # end
57
56
  # end
58
- #
59
- # def scim_schemas_url(options)
60
- # super(custom_param: 'value', **options)
61
- # end
62
57
  # end, # ...other configuration entries might follow...
63
58
 
64
59
  # If you want to support username/password authentication:
@@ -86,67 +81,6 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
86
81
  # Note that both basic and token authentication can be declared, with the
87
82
  # parameters in the inbound HTTP request determining which is invoked.
88
83
 
89
- # Scimitar rescues certain error cases and exceptions, in order to return a
90
- # JSON response to the API caller. If you want exceptions to also be
91
- # reported to a third party system such as sentry.io or raygun.com, you can
92
- # configure a Proc to do so. It is passed a Ruby exception subclass object.
93
- # For example, a minimal sentry.io reporter might do this:
94
- #
95
- # exception_reporter: Proc.new do | exception |
96
- # Sentry.capture_exception(exception)
97
- # end
98
- #
99
- # You will still need to configure your reporting system according to its
100
- # documentation (e.g. via a Rails "config/initializers/<foo>.rb" file).
101
-
102
- # Scimilar treats "VDTP" (Value, Display, Type, Primary) attribute values,
103
- # used for e.g. e-mail addresses or phone numbers, as required by default.
104
- # If you encounter a service which calls these with e.g. "null" value data,
105
- # you can configure all values to be optional. You'll need to deal with
106
- # whatever that means for you receiving system in your model code.
107
- #
108
- # optional_value_fields_required: false
109
-
110
- # The SCIM standard `/Schemas` endpoint lists, by default, all known schema
111
- # definitions with the mutabilty (read-write, read-only, write-only) state
112
- # described by those definitions, and includes all defined attributes. For
113
- # user-defined schema, this will typically exactly match your underlying
114
- # mapped attribute and model capability - it wouldn't make sense to define
115
- # your own schema that misrepresented the implementation! For core SCIM RFC
116
- # schema, though, you might want to only list actually mapped attributes.
117
- # Further, if you happen to have a non-compliant implementation especially
118
- # in relation to mutability of some attributes, you may want to report that
119
- # accurately in the '/Schemas' list, for auto-discovery purposes. To switch
120
- # to a significantly slower but more accurate render method for the list,
121
- # driven by your resource subclasses and their attribute maps, set:
122
- #
123
- # schema_list_from_attribute_mappings: [...array...]
124
- #
125
- # ...where you provide an Array of *models*, your classes that include the
126
- # Scimitar::Resources::Mixin module and, therefore, define an attribute map
127
- # translating SCIM schema attributes into actual implemented data. These
128
- # must *uniquely* describe, via the Scimitar resources they each declare in
129
- # their Scimitar::Resources::Mixin::scim_resource_type implementation, the
130
- # set of schemas and extended schemas you want to render. Should resources
131
- # share schema, the '/Schemas' endpoint will fail since it cannot determine
132
- # which model attribute map it should use and it needs the map in order to
133
- # resolve the differences (if any) between what the schema might say, and
134
- # what the actual underlying model supports.
135
- #
136
- # It is further _very_ _strongly_ _recommended_ that, for any
137
- # +scim_attributes_map+ containing a collection which has "list:" key (for
138
- # an associative array of zero or more entities; the Groups to which a User
139
- # might belong is a good example) then you should also specify the "class:"
140
- # key, giving the class used for objects in that associated collection. The
141
- # class *must* include Scimitar::Resources::Mixin, since its own attribute
142
- # map is consulted in order to render the part of the schema describing
143
- # those associated properties in the owning resource. If you don't do this,
144
- # and if you're using ActiveRecord, then Scimitar attempts association
145
- # reflection to determine the collection class - but that's more fragile
146
- # than just being told the exact class in the attribute map. No matter how
147
- # this class is determined, though, it must be possible to create a simple
148
- # instance with +new+ and no parameters, since that's needed in order to
149
- # call Scimitar::Resources::Mixin#scim_mutable_attributes.
150
84
  })
151
85
 
152
86
  end
@@ -1,38 +1,15 @@
1
- require 'rails/engine'
2
-
3
1
  module Scimitar
4
2
  class Engine < ::Rails::Engine
5
3
  isolate_namespace Scimitar
6
4
 
7
- config.autoload_once_paths = %W(
8
- #{root}/app/controllers
9
- #{root}/app/models
10
- )
11
-
12
5
  Mime::Type.register 'application/scim+json', :scim
13
6
 
14
7
  ActionDispatch::Request.parameter_parsers[Mime::Type.lookup('application/scim+json').symbol] = lambda do |body|
15
8
  JSON.parse(body)
16
9
  end
17
10
 
18
- # Return an Array of all supported default and custom resource classes.
19
- # See also :add_custom_resource and :set_default_resources.
20
- #
21
11
  def self.resources
22
- self.default_resources() + self.custom_resources()
23
- end
24
-
25
- # Returns a flat array of instances of all resource schema included in the
26
- # resource classes returned by ::resources.
27
- #
28
- def self.schemas
29
- self.resources().map(&:schemas).flatten.uniq.map(&:new)
30
- end
31
-
32
- # Returns the list of custom resources, if any.
33
- #
34
- def self.custom_resources
35
- @custom_resources ||= []
12
+ default_resources + custom_resources
36
13
  end
37
14
 
38
15
  # Can be used to add a new resource type which is not provided by the gem.
@@ -53,7 +30,7 @@ module Scimitar
53
30
  # Scimitar::Engine.add_custom_resource Scim::Resources::ShinyResource
54
31
  #
55
32
  def self.add_custom_resource(resource)
56
- self.custom_resources() << resource
33
+ custom_resources << resource
57
34
  end
58
35
 
59
36
  # Resets the resource list to default. This is really only intended for use
@@ -63,45 +40,23 @@ module Scimitar
63
40
  @custom_resources = []
64
41
  end
65
42
 
66
- # Returns the default resources added in this gem - by default, these are:
67
- #
68
- # * Scimitar::Resources::User
69
- # * Scimitar::Resources::Group
70
- #
71
- # ...but if an implementation does not e.g. support Group, it can
72
- # be overridden via ::set_default_resources to help with service
73
- # auto-discovery.
43
+ # Returns the list of custom resources, if any.
74
44
  #
75
- def self.default_resources
76
- @standard_default_resources = [ Resources::User, Resources::Group ]
77
- @default_resources ||= @standard_default_resources.dup()
45
+ def self.custom_resources
46
+ @custom_resources ||= []
78
47
  end
79
48
 
80
- # Override the resources returned by ::default_resources.
49
+ # Returns the default resources added in this gem:
81
50
  #
82
- # +resource_array+:: An Array containing one or both of
83
- # Scimitar::Resources::User and/or
84
- # Scimitar::Resources::Group, and nothing else.
51
+ # * Scimitar::Resources::User
52
+ # * Scimitar::Resources::Group
85
53
  #
86
- def self.set_default_resources(resource_array)
87
- self.default_resources()
88
- unrecognised_resources = resource_array - @standard_default_resources
89
-
90
- if unrecognised_resources.any?
91
- raise "Scimitar::Engine.set_default_resources: Only #{@standard_default_resources.map(&:name).join(', ')} are supported"
92
- elsif resource_array.empty?
93
- raise 'Scimitar::Engine.set_default_resources: At least one resource must be given'
94
- end
95
-
96
- @default_resources = resource_array
54
+ def self.default_resources
55
+ [ Resources::User, Resources::Group ]
97
56
  end
98
57
 
99
- # Resets the default resource list. This is really only intended for use
100
- # during testing, to avoid one test polluting another.
101
- #
102
- def self.reset_default_resources
103
- self.default_resources()
104
- @default_resources = @standard_default_resources
58
+ def self.schemas
59
+ resources.map(&:schemas).flatten.uniq.map(&:new)
105
60
  end
106
61
 
107
62
  end
@@ -23,8 +23,8 @@ class Hash
23
23
  #
24
24
  def self.deep_indifferent_case_insensitive_access(object)
25
25
  if object.is_a?(Hash)
26
- new_hash = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
27
- object.each do | key, value |
26
+ new_hash = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new(object)
27
+ new_hash.each do | key, value |
28
28
  new_hash[key] = deep_indifferent_case_insensitive_access(value)
29
29
  end
30
30
  new_hash
@@ -49,164 +49,34 @@ module Scimitar
49
49
  # in a case-insensitive fashion too.
50
50
  #
51
51
  # During enumeration, Hash keys will always be returned in whatever case
52
- # they were originally set. Just as with
53
- # ActiveSupport::HashWithIndifferentAccess, though, the type of the keys is
54
- # always returned as a String, even if originally set as a Symbol - only
55
- # the upper/lower case nature of the original key is preserved.
56
- #
57
- # If a key is written more than once with the same effective meaning in a
58
- # to-string, to-downcase form, then whatever case was used *first* wins;
59
- # e.g. if you did hash['User'] = 23, then hash['USER'] = 42, the result
60
- # would be {"User" => 42}.
61
- #
62
- # It's important to remember that Hash#merge is shallow and replaces values
63
- # found at existing keys in the target ("this") hash with values in the
64
- # inbound Hash. If that new value that is itself a Hash, this *replaces*
65
- # the value. For example:
66
- #
67
- # * Original: <tt>'Foo' => { 'Bar' => 42 }</tt>
68
- # * Merge: <tt>'FOO' => { 'BAR' => 24 }</tt>
69
- #
70
- # ...results in "this" target hash's key +Foo+ being addressed in the merge
71
- # by inbound key +FOO+, so the case doesn't change. But the value for +Foo+
72
- # is _replaced_ by the merging-in Hash completely:
73
- #
74
- # * Result: <tt>'Foo' => { 'BAR' => 24 }</tt>
75
- #
76
- # ...and of course we might've replaced with a totally different type, such
77
- # as +true+:
78
- #
79
- # * Original: <tt>'Foo' => { 'Bar' => 42 }</tt>
80
- # * Merge: <tt>'FOO' => true</tt>
81
- # * Result: <tt>'Foo' => true</tt>
82
- #
83
- # If you're intending to merge nested Hashes, then use ActiveSupport's
84
- # #deep_merge or an equivalent. This will have the expected outcome, where
85
- # the hash with 'BAR' is _merged_ into the existing value and, therefore,
86
- # the original 'Bar' key case is preserved:
87
- #
88
- # * Original: <tt>'Foo' => { 'Bar' => 42 }</tt>
89
- # * Deep merge: <tt>'FOO' => { 'BAR' => 24 }</tt>
90
- # * Result: <tt>'Foo' => { 'Bar' => 24 }</tt>
52
+ # they were originally set.
91
53
  #
92
54
  class HashWithIndifferentCaseInsensitiveAccess < ActiveSupport::HashWithIndifferentAccess
93
55
  def with_indifferent_case_insensitive_access
94
56
  self
95
57
  end
96
58
 
97
- def initialize(constructor = nil)
98
- @scimitar_hash_with_indifferent_case_insensitive_access_key_map = {}
99
- super
100
- end
101
-
102
- # It's vital that the attribute map is carried over when one of these
103
- # objects is duplicated. Duplication of this ivar state does *not* happen
104
- # when 'dup' is called on our superclass, so we have to do that manually.
105
- #
106
- def dup
107
- duplicate = super
108
- duplicate.instance_variable_set(
109
- '@scimitar_hash_with_indifferent_case_insensitive_access_key_map',
110
- @scimitar_hash_with_indifferent_case_insensitive_access_key_map
111
- )
112
-
113
- return duplicate
114
- end
115
-
116
- # Override the individual key writer.
117
- #
118
- def []=(key, value)
119
- string_key = scimitar_hash_with_indifferent_case_insensitive_access_string(key)
120
- indifferent_key = scimitar_hash_with_indifferent_case_insensitive_access_downcase(string_key)
121
- converted_value = convert_value(value, conversion: :assignment)
122
-
123
- # Note '||=', as there might have been a prior use of the "same" key in
124
- # a different case. The earliest one is preserved since the actual Hash
125
- # underneath all this is already using that variant of the key.
126
- #
127
- key_for_writing = (
128
- @scimitar_hash_with_indifferent_case_insensitive_access_key_map[indifferent_key] ||= string_key
129
- )
130
-
131
- regular_writer(key_for_writing, converted_value)
132
- end
133
-
134
- # Override #merge to express it in terms of #merge! (also overridden), so
135
- # that merged hashes can have their keys treated indifferently too.
136
- #
137
- def merge(*other_hashes, &block)
138
- dup.merge!(*other_hashes, &block)
139
- end
140
-
141
- # Modifies-self version of #merge, overriding Hash#merge!.
142
- #
143
- def merge!(*hashes_to_merge_to_self, &block)
144
- if block_given?
145
- hashes_to_merge_to_self.each do |hash_to_merge_to_self|
146
- hash_to_merge_to_self.each_pair do |key, value|
147
- value = block.call(key, self[key], value) if self.key?(key)
148
- self[key] = value
149
- end
150
- end
151
- else
152
- hashes_to_merge_to_self.each do |hash_to_merge_to_self|
153
- hash_to_merge_to_self.each_pair do |key, value|
154
- self[key] = value
155
- end
156
- end
157
- end
158
-
159
- self
160
- end
161
-
162
- # =======================================================================
163
- # PRIVATE INSTANCE METHODS
164
- # =======================================================================
165
- #
166
59
  private
167
60
 
168
61
  if Symbol.method_defined?(:name)
169
- def scimitar_hash_with_indifferent_case_insensitive_access_string(key)
170
- key.kind_of?(Symbol) ? key.name : key
62
+ def convert_key(key)
63
+ key.kind_of?(Symbol) ? key.name.downcase : key.downcase
171
64
  end
172
65
  else
173
- def scimitar_hash_with_indifferent_case_insensitive_access_string(key)
174
- key.kind_of?(Symbol) ? key.to_s : key
175
- end
176
- end
177
-
178
- def scimitar_hash_with_indifferent_case_insensitive_access_downcase(key)
179
- key.kind_of?(String) ? key.downcase : key
180
- end
181
-
182
- def convert_key(key)
183
- string_key = scimitar_hash_with_indifferent_case_insensitive_access_string(key)
184
- indifferent_key = scimitar_hash_with_indifferent_case_insensitive_access_downcase(string_key)
185
-
186
- @scimitar_hash_with_indifferent_case_insensitive_access_key_map[indifferent_key] || string_key
187
- end
188
-
189
- def convert_value(value, conversion: nil)
190
- if value.is_a?(Hash)
191
- if conversion == :to_hash
192
- value.to_hash
193
- else
194
- value.with_indifferent_case_insensitive_access
195
- end
196
- else
197
- super
66
+ def convert_key(key)
67
+ key.kind_of?(Symbol) ? key.to_s.downcase : key.downcase
198
68
  end
199
69
  end
200
70
 
201
71
  def update_with_single_argument(other_hash, block)
202
- if other_hash.is_a?(HashWithIndifferentCaseInsensitiveAccess)
72
+ if other_hash.is_a? HashWithIndifferentCaseInsensitiveAccess
203
73
  regular_update(other_hash, &block)
204
74
  else
205
75
  other_hash.to_hash.each_pair do |key, value|
206
76
  if block && key?(key)
207
- value = block.call(self.convert_key(key), self[key], value)
77
+ value = block.call(convert_key(key), self[key], value)
208
78
  end
209
- self.[]=(key, value)
79
+ regular_writer(convert_key(key), convert_value(value))
210
80
  end
211
81
  end
212
82
  end
@@ -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.11.0'
6
+ VERSION = '2.0.0'
7
7
 
8
8
  # Date for VERSION. If this changes, be sure to re-run "bundle install"
9
9
  # or "bundle update".
10
10
  #
11
- DATE = '2024-10-22'
11
+ DATE = '2022-03-04'
12
12
 
13
13
  end
data/lib/scimitar.rb CHANGED
@@ -1,13 +1,10 @@
1
1
  require 'scimitar/version'
2
2
  require 'scimitar/support/hash_with_indifferent_case_insensitive_access'
3
- require 'scimitar/support/utilities'
4
3
  require 'scimitar/engine'
5
4
 
6
5
  module Scimitar
7
6
  def self.service_provider_configuration=(custom_configuration)
8
- if @service_provider_configuration.nil? || ! custom_configuration.uses_defaults
9
- @service_provider_configuration = custom_configuration
10
- end
7
+ @service_provider_configuration = custom_configuration
11
8
  end
12
9
 
13
10
  def self.service_provider_configuration(location:)
@@ -17,9 +14,7 @@ module Scimitar
17
14
  end
18
15
 
19
16
  def self.engine_configuration=(custom_configuration)
20
- if @engine_configuration.nil? || ! custom_configuration.uses_defaults
21
- @engine_configuration = custom_configuration
22
- end
17
+ @engine_configuration = custom_configuration
23
18
  end
24
19
 
25
20
  def self.engine_configuration
@@ -1,4 +1,4 @@
1
- class MockGroupsController < Scimitar::ActiveRecordBackedResourcesController
1
+ class MockUsersController < 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_primary_key(id)
61
+ MockUser.find_by_id(id)
62
62
  when 'group'
63
63
  MockGroup.find_by_id(id)
64
64
  else