scimitar 1.0.3 → 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.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +2 -2
- data/app/models/scimitar/complex_types/base.rb +43 -1
- data/app/models/scimitar/errors.rb +1 -1
- data/app/models/scimitar/resources/base.rb +22 -4
- data/app/models/scimitar/resources/mixin.rb +34 -26
- data/app/models/scimitar/schema/attribute.rb +1 -1
- data/app/models/scimitar/schema/base.rb +6 -2
- data/config/initializers/scimitar.rb +71 -67
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +86 -0
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +1 -0
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +10 -8
- data/spec/models/scimitar/resources/base_spec.rb +108 -58
- data/spec/models/scimitar/resources/mixin_spec.rb +316 -264
- data/spec/models/scimitar/resources/user_spec.rb +17 -4
- data/spec/requests/active_record_backed_resources_controller_spec.rb +172 -127
- data/spec/requests/application_controller_spec.rb +0 -1
- data/spec/requests/engine_spec.rb +26 -1
- data/spec/spec_helper.rb +27 -0
- data/spec/spec_helper_spec.rb +30 -0
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +61 -0
- metadata +58 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95a2166cc921a400959f9d8d4398f6bf8ecb772f8d7a0a0a73950892e85d808a
|
4
|
+
data.tar.gz: cdf5aab3812f10f69c96304e738a150f4208850267527b66d36eeb99548d7b1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0925517599b107e44fd93db9be142aebe608892c2c5069c50d22b353c51238290710474b062a002fdb010be3d783c4dea3b314f72f47b4aca3c2385a8fc1377
|
7
|
+
data.tar.gz: eef6eebfc64bb2d4adabfca110f26e3a6c9e227f47387da6fb384925899ddf0ae260cf68176f24040a6bb356cf34f6576c920bd44264d4a1fee415aeadc237e6
|
@@ -131,8 +131,8 @@ module Scimitar
|
|
131
131
|
raise NotImplementedError
|
132
132
|
end
|
133
133
|
|
134
|
-
# Find a
|
135
|
-
#
|
134
|
+
# Find a record by ID. Subclasses can override this if they need special
|
135
|
+
# lookup behaviour.
|
136
136
|
#
|
137
137
|
# +record_id+:: Record ID (SCIM schema 'id' value - "our" ID).
|
138
138
|
#
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Scimitar
|
2
4
|
module ComplexTypes
|
3
5
|
|
@@ -22,8 +24,48 @@ module Scimitar
|
|
22
24
|
include Scimitar::Schema::DerivedAttributes
|
23
25
|
include Scimitar::Errors
|
24
26
|
|
27
|
+
# Instantiates with attribute values - see ActiveModel::Model#initialize.
|
28
|
+
#
|
29
|
+
# Allows case-insensitive attributes given in options, by enumerating all
|
30
|
+
# instance methods that exist in the subclass (at the point where this
|
31
|
+
# method runs, 'self' is a subclass, unless someone instantiated this
|
32
|
+
# base class directly) and subtracting methods in the base class. Usually
|
33
|
+
# this leaves just the attribute accessors, with not much extra.
|
34
|
+
#
|
35
|
+
# Map a normalized case version of those names to the actual method names
|
36
|
+
# then for each key in the inbound options, normalize it and see if one
|
37
|
+
# of the actual case method names is available. If so, use that instead.
|
38
|
+
#
|
39
|
+
# Unmapped option keys will most likely have no corresponding writer
|
40
|
+
# method in the subclass and NoMethodError will therefore arise.
|
41
|
+
#
|
25
42
|
def initialize(options={})
|
26
|
-
|
43
|
+
normalized_method_map = HashWithIndifferentAccess.new
|
44
|
+
corrected_options = {}
|
45
|
+
probable_accessor_methods = self.class.instance_methods - self.class.superclass.instance_methods
|
46
|
+
|
47
|
+
unless options.empty?
|
48
|
+
probable_accessor_methods.each do | method_name |
|
49
|
+
next if method_name.end_with?('=')
|
50
|
+
normalized_method_map[method_name.downcase] = method_name
|
51
|
+
end
|
52
|
+
|
53
|
+
options.each do | ambiguous_case_name, value |
|
54
|
+
normalized_name = ambiguous_case_name.downcase
|
55
|
+
corrected_name = normalized_method_map[normalized_name]
|
56
|
+
|
57
|
+
if corrected_name.nil?
|
58
|
+
corrected_options[ambiguous_case_name] = value # Probably will lead to NoMethodError
|
59
|
+
else
|
60
|
+
corrected_options[corrected_name] = value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
options = corrected_options
|
65
|
+
end
|
66
|
+
|
67
|
+
super # Calls into ActiveModel::Model
|
68
|
+
|
27
69
|
@errors = ActiveModel::Errors.new(self)
|
28
70
|
end
|
29
71
|
|
@@ -11,10 +11,27 @@ module Scimitar
|
|
11
11
|
validate :validate_resource
|
12
12
|
|
13
13
|
def initialize(options = {})
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
flattened_attributes = flatten_extension_attributes(options)
|
15
|
+
ci_all_attributes = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
|
16
|
+
camel_attributes = {}
|
17
|
+
|
18
|
+
# Create a map where values are the schema-correct-case attribute names
|
19
|
+
# and the values are set the same, but since the ci_all_attributes data
|
20
|
+
# type is HashWithIndifferentCaseInsensitiveAccess, lookups in this are
|
21
|
+
# case insensitive. Thus, arbitrary case input data can be mapped to
|
22
|
+
# the case correctness required for ActiveModel's attribute accessors.
|
23
|
+
#
|
24
|
+
self.class.all_attributes.each { |attr| ci_all_attributes[attr] = attr }
|
25
|
+
|
26
|
+
flattened_attributes.each do | key, value |
|
27
|
+
if ci_all_attributes.key?(key)
|
28
|
+
camel_attributes[ci_all_attributes[key]] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
super(camel_attributes)
|
33
|
+
constantize_complex_types(camel_attributes)
|
34
|
+
|
18
35
|
@errors = ActiveModel::Errors.new(self)
|
19
36
|
end
|
20
37
|
|
@@ -109,6 +126,7 @@ module Scimitar
|
|
109
126
|
def constantize_complex_types(hash)
|
110
127
|
hash.with_indifferent_access.each_pair do |attr_name, attr_value|
|
111
128
|
scim_attribute = self.class.complex_scim_attributes[attr_name].try(:first)
|
129
|
+
|
112
130
|
if scim_attribute && scim_attribute.complexType
|
113
131
|
if scim_attribute.multiValued
|
114
132
|
self.send("#{attr_name}=", attr_value.map {|attr_for_each_item| complex_type_from_hash(scim_attribute, attr_for_each_item)})
|
@@ -404,10 +404,10 @@ module Scimitar
|
|
404
404
|
# Call ONLY for PATCH. For POST and PUT, see #from_scim!.
|
405
405
|
#
|
406
406
|
def from_scim_patch!(patch_hash:)
|
407
|
-
patch_hash.freeze()
|
408
|
-
|
407
|
+
frozen_ci_patch_hash = patch_hash.with_indifferent_case_insensitive_access().freeze()
|
408
|
+
ci_scim_hash = self.to_scim(location: '(unused)').as_json().with_indifferent_case_insensitive_access()
|
409
409
|
|
410
|
-
|
410
|
+
frozen_ci_patch_hash['operations'].each do |operation|
|
411
411
|
nature = operation['op' ]&.downcase
|
412
412
|
path_str = operation['path' ]
|
413
413
|
value = operation['value']
|
@@ -440,22 +440,22 @@ module Scimitar
|
|
440
440
|
if path_str.blank?
|
441
441
|
extract_root = true
|
442
442
|
path_str = 'root'
|
443
|
-
|
443
|
+
ci_scim_hash = { 'root' => ci_scim_hash }.with_indifferent_case_insensitive_access()
|
444
444
|
end
|
445
445
|
|
446
446
|
self.from_patch_backend!(
|
447
447
|
nature: nature,
|
448
448
|
path: (path_str || '').split('.'),
|
449
449
|
value: value,
|
450
|
-
altering_hash:
|
450
|
+
altering_hash: ci_scim_hash
|
451
451
|
)
|
452
452
|
|
453
453
|
if extract_root
|
454
|
-
|
454
|
+
ci_scim_hash = ci_scim_hash['root']
|
455
455
|
end
|
456
456
|
end
|
457
457
|
|
458
|
-
self.from_scim!(scim_hash:
|
458
|
+
self.from_scim!(scim_hash: ci_scim_hash)
|
459
459
|
return self
|
460
460
|
end
|
461
461
|
|
@@ -542,20 +542,20 @@ module Scimitar
|
|
542
542
|
# ::scim_attributes_map.
|
543
543
|
#
|
544
544
|
# { | {
|
545
|
-
# "userName": "foo", |
|
546
|
-
# "name": { |
|
547
|
-
# "givenName": "Foo", |
|
548
|
-
# "familyName": "Bar" |
|
549
|
-
# }, |
|
550
|
-
# "active": true, |
|
545
|
+
# "userName": "foo", | 'id': :id,
|
546
|
+
# "name": { | 'externalId': :scim_uid,
|
547
|
+
# "givenName": "Foo", | 'userName': :username,
|
548
|
+
# "familyName": "Bar" | 'name': {
|
549
|
+
# }, | 'givenName': :first_name,
|
550
|
+
# "active": true, | 'familyName': :last_name
|
551
551
|
# "emails": [ | },
|
552
|
-
# { |
|
552
|
+
# { | 'emails': [
|
553
553
|
# "type": "work", <------\ | {
|
554
|
-
# "primary": true, \------+---
|
555
|
-
# "value": "foo.bar@test.com" |
|
556
|
-
# } |
|
557
|
-
# ], |
|
558
|
-
# "phoneNumbers": [ |
|
554
|
+
# "primary": true, \------+--- 'match': 'type',
|
555
|
+
# "value": "foo.bar@test.com" | 'with': 'work',
|
556
|
+
# } | 'using': {
|
557
|
+
# ], | 'value': :work_email_address,
|
558
|
+
# "phoneNumbers": [ | 'primary': true
|
559
559
|
# { | }
|
560
560
|
# "type": "work", | }
|
561
561
|
# "primary": false, | ],
|
@@ -568,7 +568,7 @@ module Scimitar
|
|
568
568
|
# "location": "https://test.com/mock_users/42", | }
|
569
569
|
# "resourceType": "User" | }
|
570
570
|
# }, | ],
|
571
|
-
# "schemas": [ |
|
571
|
+
# "schemas": [ | 'active': :is_active
|
572
572
|
# "urn:ietf:params:scim:schemas:core:2.0:User" | }
|
573
573
|
# ] |
|
574
574
|
# } |
|
@@ -600,7 +600,7 @@ module Scimitar
|
|
600
600
|
scim_hash_or_leaf_value:,
|
601
601
|
path: []
|
602
602
|
)
|
603
|
-
|
603
|
+
scim_hash_or_leaf_value = scim_hash_or_leaf_value.with_indifferent_case_insensitive_access() if scim_hash_or_leaf_value.is_a?(Hash)
|
604
604
|
|
605
605
|
# We get the schema via this instance's class's resource type, even
|
606
606
|
# if we end up in collections of other types - because it's *this*
|
@@ -668,7 +668,7 @@ module Scimitar
|
|
668
668
|
end # "map_entry&.each do | mapped_array_entry |"
|
669
669
|
|
670
670
|
when Symbol # Setter/getter method at leaf position in attribute map
|
671
|
-
if path ==
|
671
|
+
if path.length == 1 && path.first&.to_s&.downcase == 'externalid' # Special case held only in schema base class
|
672
672
|
mutable = true
|
673
673
|
else
|
674
674
|
attribute = resource_class.find_attribute(*path)
|
@@ -706,7 +706,8 @@ module Scimitar
|
|
706
706
|
#
|
707
707
|
# +altering_hash+:: The Hash to operate on at the current +path+. For
|
708
708
|
# recursive calls, this will be some way down into
|
709
|
-
# the SCIM representation of 'self'.
|
709
|
+
# the SCIM representation of 'self'. MUST be a
|
710
|
+
# HashWithIndifferentCaseInsensitiveAccess.
|
710
711
|
#
|
711
712
|
# Note that SCIM PATCH operations permit *no* path for 'replace' and
|
712
713
|
# 'add' operations, meaning "apply to whole object". To avoid special
|
@@ -715,6 +716,7 @@ module Scimitar
|
|
715
716
|
# interest and supply this key as the sole array entry in +path+.
|
716
717
|
#
|
717
718
|
def from_patch_backend!(nature:, path:, value:, altering_hash:)
|
719
|
+
raise 'Case sensitivity violation' unless altering_hash.is_a?(Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess)
|
718
720
|
|
719
721
|
# These all throw exceptions if data is not as expected / required,
|
720
722
|
# any of which are rescued below.
|
@@ -752,6 +754,8 @@ module Scimitar
|
|
752
754
|
# Happily throws exceptions if data is not as expected / required.
|
753
755
|
#
|
754
756
|
def from_patch_backend_traverse!(nature:, path:, value:, altering_hash:)
|
757
|
+
raise 'Case sensitivity violation' unless altering_hash.is_a?(Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess)
|
758
|
+
|
755
759
|
path_component, filter = extract_filter_from(path_component: path.first)
|
756
760
|
|
757
761
|
# https://tools.ietf.org/html/rfc7644#section-3.5.2.1
|
@@ -766,7 +770,7 @@ module Scimitar
|
|
766
770
|
#
|
767
771
|
# Harmless in this context for 'remove'.
|
768
772
|
#
|
769
|
-
altering_hash[path_component] ||=
|
773
|
+
altering_hash[path_component] ||= Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
|
770
774
|
|
771
775
|
# Unless the PATCH is bad, inner data is an Array or Hash always as
|
772
776
|
# by definition this method is only called at path positions above
|
@@ -784,7 +788,7 @@ module Scimitar
|
|
784
788
|
# Same reason as section 3.5.2.1 / 3.5.2.3 RFC quotes above.
|
785
789
|
#
|
786
790
|
if nature != 'remove' && matched_hashes.empty?
|
787
|
-
new_hash =
|
791
|
+
new_hash = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
|
788
792
|
altering_hash[path_component] = [new_hash]
|
789
793
|
matched_hashes = [new_hash]
|
790
794
|
end
|
@@ -815,6 +819,8 @@ module Scimitar
|
|
815
819
|
# Happily throws exceptions if data is not as expected / required.
|
816
820
|
#
|
817
821
|
def from_patch_backend_apply!(nature:, path:, value:, altering_hash:)
|
822
|
+
raise 'Case sensitivity violation' unless altering_hash.is_a?(Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess)
|
823
|
+
|
818
824
|
path_component, filter = extract_filter_from(path_component: path.first)
|
819
825
|
current_data_at_path = altering_hash[path_component]
|
820
826
|
|
@@ -968,7 +974,9 @@ module Scimitar
|
|
968
974
|
value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
|
969
975
|
|
970
976
|
within_array.each.with_index do | hash, index |
|
971
|
-
|
977
|
+
ci_hash = hash.with_indifferent_case_insensitive_access()
|
978
|
+
matched = ci_hash.key?(attribute) && ci_hash[attribute]&.to_s == value&.to_s
|
979
|
+
|
972
980
|
yield(hash, index) if matched
|
973
981
|
end
|
974
982
|
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
|
-
|
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
|
|
@@ -62,8 +64,10 @@ module Scimitar
|
|
62
64
|
current_path_entry = path.shift()
|
63
65
|
next if current_path_entry.is_a?(Integer) # Skip array indicies arising from multi-value attributes
|
64
66
|
|
67
|
+
current_path_entry = current_path_entry.to_s.downcase
|
68
|
+
|
65
69
|
found_attribute = current_attributes.find do | attribute_to_check |
|
66
|
-
attribute_to_check.name == current_path_entry
|
70
|
+
attribute_to_check.name.to_s.downcase == current_path_entry
|
67
71
|
end
|
68
72
|
|
69
73
|
if found_attribute && path.present? # Any sub-attributes to check?...
|
@@ -2,81 +2,85 @@
|
|
2
2
|
#
|
3
3
|
# For supporting information and rationale, please see README.md.
|
4
4
|
|
5
|
-
#
|
6
|
-
# SERVICE PROVIDER CONFIGURATION
|
7
|
-
# =============================================================================
|
8
|
-
#
|
9
|
-
# This is a Ruby abstraction over a SCIM entity that declares the capabilities
|
10
|
-
# supported by a particular implementation.
|
11
|
-
#
|
12
|
-
# Typically this is used to declare parts of the standard unsupported, if you
|
13
|
-
# don't need them and don't want to provide subclass support.
|
14
|
-
#
|
15
|
-
Scimitar.service_provider_configuration = Scimitar::ServiceProviderConfiguration.new({
|
5
|
+
Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
16
6
|
|
17
|
-
#
|
7
|
+
# ===========================================================================
|
8
|
+
# SERVICE PROVIDER CONFIGURATION
|
9
|
+
# ===========================================================================
|
18
10
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# that filters are not supported so that calling clients shouldn't use them:
|
11
|
+
# This is a Ruby abstraction over a SCIM entity that declares the
|
12
|
+
# capabilities supported by a particular implementation.
|
22
13
|
#
|
23
|
-
#
|
14
|
+
# Typically this is used to declare parts of the standard unsupported, if you
|
15
|
+
# don't need them and don't want to provide subclass support.
|
16
|
+
#
|
17
|
+
Scimitar.service_provider_configuration = Scimitar::ServiceProviderConfiguration.new({
|
24
18
|
|
25
|
-
|
19
|
+
# See https://tools.ietf.org/html/rfc7643#section-8.5 for properties.
|
20
|
+
#
|
21
|
+
# See Gem file 'app/models/scimitar/service_provider_configuration.rb'
|
22
|
+
# for defaults. Define Hash keys here that override defaults; e.g. to
|
23
|
+
# declare that filters are not supported so that calling clients shouldn't
|
24
|
+
# use them:
|
25
|
+
#
|
26
|
+
# filter: Scimitar::Supported.unsupported
|
26
27
|
|
27
|
-
|
28
|
-
# ENGINE CONFIGURATION
|
29
|
-
# =============================================================================
|
30
|
-
#
|
31
|
-
# This is where you provide callbacks for things like authorisation or mixins
|
32
|
-
# that get included into all Scimitar-derived controllers (for things like
|
33
|
-
# before-actions that apply to all Scimitar controller-based routes).
|
34
|
-
#
|
35
|
-
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
28
|
+
})
|
36
29
|
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# For example:
|
30
|
+
# ===========================================================================
|
31
|
+
# ENGINE CONFIGURATION
|
32
|
+
# ===========================================================================
|
42
33
|
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
34
|
+
# This is where you provide callbacks for things like authorisation or mixins
|
35
|
+
# that get included into all Scimitar-derived controllers (for things like
|
36
|
+
# before-actions that apply to all Scimitar controller-based routes).
|
46
37
|
#
|
47
|
-
|
48
|
-
# # one of your controller classes, but it gets included in all
|
49
|
-
# # Scimitar classes too.
|
50
|
-
#
|
51
|
-
# skip_before_action :verify_authenticity_token
|
52
|
-
# prepend_before_action :setup_some_kind_of_multi_tenancy_data
|
53
|
-
# end
|
54
|
-
# end
|
55
|
-
# end, # ...other configuration entries might follow...
|
38
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
56
39
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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:
|
44
|
+
#
|
45
|
+
# application_controller_mixin: Module.new do
|
46
|
+
# def self.included(base)
|
47
|
+
# base.class_eval do
|
48
|
+
#
|
49
|
+
# # Anything here is written just as you'd write it at the top of
|
50
|
+
# # one of your controller classes, but it gets included in all
|
51
|
+
# # Scimitar classes too.
|
52
|
+
#
|
53
|
+
# skip_before_action :verify_authenticity_token
|
54
|
+
# prepend_before_action :setup_some_kind_of_multi_tenancy_data
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
# end, # ...other configuration entries might follow...
|
67
58
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
59
|
+
# If you want to support username/password authentication:
|
60
|
+
#
|
61
|
+
# basic_authenticator: Proc.new do | username, password |
|
62
|
+
# # Check username/password and return 'true' if valid, else 'false'.
|
63
|
+
# end, # ...other configuration entries might follow...
|
64
|
+
#
|
65
|
+
# The 'username' and 'password' parameters come from Rails:
|
66
|
+
#
|
67
|
+
# https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic.html
|
68
|
+
# https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#method-i-authenticate_with_http_basic
|
69
|
+
|
70
|
+
# If you want to support HTTP bearer token (OAuth-style) authentication:
|
71
|
+
#
|
72
|
+
# token_authenticator: Proc.new do | token, options |
|
73
|
+
# # Check token and return 'true' if valid, else 'false'.
|
74
|
+
# end, # ...other configuration entries might follow...
|
75
|
+
#
|
76
|
+
# The 'token' and 'options' parameters come from Rails:
|
77
|
+
#
|
78
|
+
# https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html
|
79
|
+
# https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token/ControllerMethods.html#method-i-authenticate_with_http_token
|
80
|
+
#
|
81
|
+
# Note that both basic and token authentication can be declared, with the
|
82
|
+
# parameters in the inbound HTTP request determining which is invoked.
|
83
|
+
|
84
|
+
})
|
81
85
|
|
82
|
-
|
86
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'active_support/hash_with_indifferent_access'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
|
5
|
+
# Converts this Hash to an instance of
|
6
|
+
# Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess, which is
|
7
|
+
# a subclass of ActiveSupport::HashWithIndifferentAccess with the addition of
|
8
|
+
# case-insensitive lookup.
|
9
|
+
#
|
10
|
+
# Note that this is more thorough than the ActiveSupport counterpart. It
|
11
|
+
# converts recursively, so that all Hashes to arbitrary depth, including any
|
12
|
+
# hashes inside Arrays, are converted. This is an expensive operation.
|
13
|
+
#
|
14
|
+
def with_indifferent_case_insensitive_access
|
15
|
+
self.class.deep_indifferent_case_insensitive_access(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Supports #with_indifferent_case_insensitive_access. Converts the given item
|
19
|
+
# to indifferent, case-insensitive access as a Hash; or converts Array items
|
20
|
+
# if given an Array; or returns the given object.
|
21
|
+
#
|
22
|
+
# Hashes and Arrays at all depths are duplicated as a result.
|
23
|
+
#
|
24
|
+
def self.deep_indifferent_case_insensitive_access(object)
|
25
|
+
if object.is_a?(Hash)
|
26
|
+
new_hash = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new(object)
|
27
|
+
new_hash.each do | key, value |
|
28
|
+
new_hash[key] = deep_indifferent_case_insensitive_access(value)
|
29
|
+
end
|
30
|
+
new_hash
|
31
|
+
|
32
|
+
elsif object.is_a?(Array)
|
33
|
+
object.map do | array_entry |
|
34
|
+
deep_indifferent_case_insensitive_access(array_entry)
|
35
|
+
end
|
36
|
+
|
37
|
+
else
|
38
|
+
object
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Scimitar
|
45
|
+
module Support
|
46
|
+
|
47
|
+
# A subclass of ActiveSupport::HashWithIndifferentAccess where not only
|
48
|
+
# can Hash keys be queried as Symbols or Strings, but they are looked up
|
49
|
+
# in a case-insensitive fashion too.
|
50
|
+
#
|
51
|
+
# During enumeration, Hash keys will always be returned in whatever case
|
52
|
+
# they were originally set.
|
53
|
+
#
|
54
|
+
class HashWithIndifferentCaseInsensitiveAccess < ActiveSupport::HashWithIndifferentAccess
|
55
|
+
def with_indifferent_case_insensitive_access
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
if Symbol.method_defined?(:name)
|
62
|
+
def convert_key(key)
|
63
|
+
key.kind_of?(Symbol) ? key.name.downcase : key.downcase
|
64
|
+
end
|
65
|
+
else
|
66
|
+
def convert_key(key)
|
67
|
+
key.kind_of?(Symbol) ? key.to_s.downcase : key.downcase
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_with_single_argument(other_hash, block)
|
72
|
+
if other_hash.is_a? HashWithIndifferentCaseInsensitiveAccess
|
73
|
+
regular_update(other_hash, &block)
|
74
|
+
else
|
75
|
+
other_hash.to_hash.each_pair do |key, value|
|
76
|
+
if block && key?(key)
|
77
|
+
value = block.call(convert_key(key), self[key], value)
|
78
|
+
end
|
79
|
+
regular_writer(convert_key(key), convert_value(value))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/scimitar/version.rb
CHANGED
@@ -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 = '
|
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 = '
|
11
|
+
DATE = '2022-03-04'
|
12
12
|
|
13
13
|
end
|
data/lib/scimitar.rb
CHANGED
@@ -1,15 +1,38 @@
|
|
1
|
+
require 'active_support/core_ext/integer/time'
|
2
|
+
|
1
3
|
Rails.application.configure do
|
2
4
|
config.cache_classes = true
|
3
5
|
config.eager_load = false
|
4
|
-
config.serve_static_files = true
|
5
|
-
config.static_cache_control = 'public, max-age=3600'
|
6
|
-
config.consider_all_requests_local = true
|
7
6
|
|
8
|
-
|
7
|
+
# Configure public file server for tests with Cache-Control for performance.
|
8
|
+
config.public_file_server.enabled = true
|
9
|
+
config.public_file_server.headers = {
|
10
|
+
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
|
11
|
+
}
|
9
12
|
|
13
|
+
# Show full error reports and disable caching.
|
14
|
+
config.consider_all_requests_local = true
|
10
15
|
config.action_controller.perform_caching = false
|
16
|
+
config.cache_store = :null_store
|
17
|
+
|
18
|
+
# Raise exceptions instead of rendering exception templates.
|
19
|
+
config.action_dispatch.show_exceptions = false
|
20
|
+
|
21
|
+
# Disable request forgery protection in test environment.
|
11
22
|
config.action_controller.allow_forgery_protection = false
|
12
23
|
|
13
|
-
|
24
|
+
# Print deprecation notices to the stderr.
|
14
25
|
config.active_support.deprecation = :stderr
|
26
|
+
|
27
|
+
# Raise exceptions for disallowed deprecations.
|
28
|
+
config.active_support.disallowed_deprecation = :raise
|
29
|
+
|
30
|
+
# Tell Active Support which deprecation messages to disallow.
|
31
|
+
config.active_support.disallowed_deprecation_warnings = []
|
32
|
+
|
33
|
+
# Raises error for missing translations.
|
34
|
+
config.i18n.raise_on_missing_translations = true
|
35
|
+
|
36
|
+
# Annotate rendered view with file names.
|
37
|
+
# config.action_view.annotate_rendered_view_with_filenames = true
|
15
38
|
end
|