scimitar 1.10.0 → 2.0.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.
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 +13 -41
  4. data/app/controllers/scimitar/resource_types_controller.rb +2 -0
  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 +2 -2
  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.10.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-06-27'
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