scimitar 2.7.2 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6898633f866963ccaacd5f16120a957f698e2e1aacd51b985c483616fe5c49d5
4
- data.tar.gz: d24f2017fab7ae3432c21d5eacb281fa8cd1ac3b43a25756ccbf105a426bf85f
3
+ metadata.gz: 7524696efa05186edcbba9876a3309d3c8a8d51772e3ac0a009a9e83d8f25d16
4
+ data.tar.gz: e6cad46dc8754981f1d85cc9bcc98f73f9edddfa0d0386497b710acd11ad666c
5
5
  SHA512:
6
- metadata.gz: cefe2b8fb441822a751ba93d54b43dc6e75281cc707095f3880c9927d413d310dbcb0676405499f46e641cb767a6d50d3d03b9a0d1b6d1218fbd59e3c036caba
7
- data.tar.gz: 34bc849312df18601c49df4f7e9019ed258b74bb84a002309a5d33b8119c4776edfb1e7cbacdcebbc92533b9cf63586c33631ac505ca1bb17bc859a9d1a65078
6
+ metadata.gz: d040bbf693140f5e62dea569d02d977cb5fd966d2464c0a722bea449428de097901471a2f4b0c2380cf935c424195336d7eed4f469a8ebd019d4277dbe90c629
7
+ data.tar.gz: 15616cedc7fffc3df69dcc1b2f35e9bd3c5408e12f8cccd536d724ce2925c72e4aa9a8f63732135ba3a71d23438dc02e8835978ff37977722cd23cab63c1ee28
@@ -483,26 +483,14 @@ module Scimitar
483
483
  ci_scim_hash = { 'root' => ci_scim_hash }.with_indifferent_case_insensitive_access()
484
484
  end
485
485
 
486
- # Handle extension schema. Contributed by @bettysteger and
487
- # @MorrisFreeman via:
486
+ # Split the path into an array of path components, in a way
487
+ # which is aware of extension schemas. See documentation of
488
+ # Scimitar::Support::Utilities.path_str_to_array for details.
488
489
  #
489
- # https://github.com/RIPAGlobal/scimitar/issues/48
490
- # https://github.com/RIPAGlobal/scimitar/pull/49
491
- #
492
- # Note the ":" separating the schema ID (URN) from the attribute.
493
- # The nature of JSON rendering / other payloads might lead you to
494
- # expect a "." as with any complex types, but that's not the case;
495
- # see https://tools.ietf.org/html/rfc7644#section-3.10, or
496
- # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
497
- # particular, https://tools.ietf.org/html/rfc7644#page-35.
498
- #
499
- paths = []
500
- self.class.scim_resource_type.extended_schemas.each do |schema|
501
- path_str.downcase.split(schema.id.downcase + ':').drop(1).each do |path|
502
- paths += [schema.id] + path.split('.')
503
- end
504
- end
505
- paths = path_str.split('.') if paths.empty?
490
+ paths = ::Scimitar::Support::Utilities.path_str_to_array(
491
+ self.class.scim_resource_type.extended_schemas,
492
+ path_str
493
+ )
506
494
 
507
495
  self.from_patch_backend!(
508
496
  nature: nature,
@@ -740,9 +728,17 @@ module Scimitar
740
728
  # https://github.com/RIPAGlobal/scimitar/issues/48
741
729
  # https://github.com/RIPAGlobal/scimitar/pull/49
742
730
  #
731
+ # Note the shortcoming that attribute names within extensions
732
+ # must be unique, as this mechanism basically just pulls out
733
+ # extension attributes to the top level, losing what amounts
734
+ # to the namespace that the extension schema ID provides.
735
+ #
743
736
  attribute_tree = []
744
737
  resource_class.extended_schemas.each do |schema|
745
- attribute_tree << schema.id and break if schema.scim_attributes.any? { |attribute| attribute.name == scim_attribute.to_s }
738
+ if schema.scim_attributes.any? { |attribute| attribute.name == scim_attribute.to_s }
739
+ attribute_tree << schema.id
740
+ break # NOTE EARLY LOOP EXIT
741
+ end
746
742
  end
747
743
  attribute_tree << scim_attribute.to_s
748
744
 
@@ -950,7 +946,11 @@ module Scimitar
950
946
  end
951
947
 
952
948
  found_data_for_recursion.each do | found_data |
953
- attr_map = with_attr_map[path_component.to_sym]
949
+ attr_map = if path_component.to_sym == :root
950
+ with_attr_map
951
+ else
952
+ with_attr_map[path_component.to_sym]
953
+ end
954
954
 
955
955
  # Static array mappings need us to find the right map entry that
956
956
  # corresponds to the SCIM data at hand and recurse back into the
@@ -1091,9 +1091,27 @@ module Scimitar
1091
1091
  # at key 'members' with the above, rather than adding.
1092
1092
  #
1093
1093
  value.keys.each do | key |
1094
+
1095
+ # Handle the Azure (Entra) case where keys might use
1096
+ # dotted paths - see:
1097
+ #
1098
+ # https://github.com/RIPAGlobal/scimitar/issues/123
1099
+ #
1100
+ # ...along with keys containing schema IDs - see:
1101
+ #
1102
+ # https://is.docs.wso2.com/en/next/apis/scim2-patch-operations/#add-user-attributes
1103
+ #
1104
+ # ...and scroll down to example 3 of "Complex singular
1105
+ # attributes".
1106
+ #
1107
+ subpaths = ::Scimitar::Support::Utilities.path_str_to_array(
1108
+ self.class.scim_resource_type.extended_schemas,
1109
+ key
1110
+ )
1111
+
1094
1112
  from_patch_backend!(
1095
1113
  nature: nature,
1096
- path: path + [key],
1114
+ path: path + subpaths,
1097
1115
  value: value[key],
1098
1116
  altering_hash: altering_hash,
1099
1117
  with_attr_map: with_attr_map
@@ -1106,7 +1124,12 @@ module Scimitar
1106
1124
  when 'replace'
1107
1125
  if path_component == 'root'
1108
1126
  dot_pathed_value = value.inject({}) do |hash, (k, v)|
1109
- hash.deep_merge!(::Scimitar::Support::Utilities.dot_path(k.split('.'), v))
1127
+ subpaths = ::Scimitar::Support::Utilities.path_str_to_array(
1128
+ self.class.scim_resource_type.extended_schemas,
1129
+ k
1130
+ )
1131
+
1132
+ hash.deep_merge!(::Scimitar::Support::Utilities.dot_path(subpaths, v))
1110
1133
  end
1111
1134
 
1112
1135
  altering_hash[path_component].deep_merge!(dot_pathed_value)
@@ -46,6 +46,61 @@ module Scimitar
46
46
  hash[array.shift()] = self.dot_path(array, value)
47
47
  end
48
48
  end
49
+
50
+ # Schema ID-aware splitter handling ":" or "." separators. Adapted from
51
+ # contribution by @bettysteger and @MorrisFreeman in:
52
+ #
53
+ # https://github.com/RIPAGlobal/scimitar/issues/48
54
+ # https://github.com/RIPAGlobal/scimitar/pull/49
55
+ #
56
+ # +schemas:: Array of extension schemas, e.g. a SCIM resource class'
57
+ # <tt>scim_resource_type.extended_schemas</tt> value. The
58
+ # Array should be empty if there are no extensions.
59
+ #
60
+ # +path_str+:: Path string, e.g. <tt>"password"</tt>, <tt>"name.givenName"</tt>,
61
+ # <tt>"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"</tt> (special case),
62
+ # <tt>"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization"</tt>
63
+ #
64
+ # Returns an array of components, e.g. <tt>["password"]</tt>, <tt>["name",
65
+ # "givenName"]</tt>,
66
+ # <tt>["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"]</tt> (special case),
67
+ # <tt>["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User", "organization"]</tt>.
68
+ #
69
+ # The called-out special case is for a schema ID without any appended
70
+ # path components, which is returned as a single element ID to aid in
71
+ # traversal particularly of things like PATCH requests. There, a "value"
72
+ # attribute might have a key string that's simply a schema ID, with an
73
+ # object beneath that's got attribute-name pairs, possibly nested, in a
74
+ # path-free payload.
75
+ #
76
+ def self.path_str_to_array(schemas, path_str)
77
+ components = []
78
+
79
+ # Note the ":" separating the schema ID (URN) from the attribute.
80
+ # The nature of JSON rendering / other payloads might lead you to
81
+ # expect a "." as with any complex types, but that's not the case;
82
+ # see https://tools.ietf.org/html/rfc7644#section-3.10, or
83
+ # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
84
+ # particular, https://tools.ietf.org/html/rfc7644#page-35.
85
+ #
86
+ if path_str.include?(':')
87
+ schemas.each do |schema|
88
+ attributes_after_schema_id = path_str.downcase.split(schema.id.downcase + ':').drop(1)
89
+
90
+ if attributes_after_schema_id.empty?
91
+ components += [schema.id]
92
+ else
93
+ attributes_after_schema_id.each do |component|
94
+ components += [schema.id] + component.split('.')
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ components = path_str.split('.') if components.empty?
101
+ return components
102
+ end
103
+
49
104
  end
50
105
  end
51
106
  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 = '2.7.2'
6
+ VERSION = '2.7.3'
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-03-27'
11
+ DATE = '2024-06-11'
12
12
 
13
13
  end
@@ -764,6 +764,106 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
764
764
  expect(@u2.password).to eql('oldpassword')
765
765
  end
766
766
 
767
+ context 'which' do
768
+ shared_examples 'it handles not-to-spec in-value Azure/Entra dotted attribute paths' do | operation |
769
+ it "and performs operation" do
770
+ payload = {
771
+ Operations: [
772
+ {
773
+ op: 'add',
774
+ value: {
775
+ 'name.givenName' => 'Foo!',
776
+ 'name.familyName' => 'Bar!',
777
+ 'name.formatted' => 'Foo! Bar!' # Unrecognised; should be ignored
778
+ },
779
+ },
780
+ ]
781
+ }
782
+
783
+ payload = spec_helper_hupcase(payload) if force_upper_case
784
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
785
+
786
+ expect(response.status ).to eql(200)
787
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
788
+
789
+ @u2.reload
790
+ result = JSON.parse(response.body)
791
+
792
+ expect(@u2.first_name).to eql('Foo!')
793
+ expect(@u2.last_name ).to eql('Bar!')
794
+ end
795
+ end
796
+
797
+ it_behaves_like 'it handles not-to-spec in-value Azure/Entra dotted attribute paths', 'add'
798
+ it_behaves_like 'it handles not-to-spec in-value Azure/Entra dotted attribute paths', 'replace'
799
+
800
+ shared_examples 'it handles schema ID value keys without inline attributes' do | operation |
801
+ it "and performs operation" do
802
+ payload = {
803
+ Operations: [
804
+ {
805
+ op: operation,
806
+ value: {
807
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User': {
808
+ 'organization' => 'Foo Bar!',
809
+ 'department' => 'Bar Foo!'
810
+ },
811
+ },
812
+ },
813
+ ]
814
+ }
815
+
816
+ @u2.update!(organization: 'Old org')
817
+ payload = spec_helper_hupcase(payload) if force_upper_case
818
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
819
+
820
+ expect(response.status ).to eql(200)
821
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
822
+
823
+ @u2.reload
824
+ result = JSON.parse(response.body)
825
+
826
+ expect(@u2.organization).to eql('Foo Bar!')
827
+ expect(@u2.department ).to eql('Bar Foo!')
828
+ end
829
+ end
830
+
831
+ it_behaves_like 'it handles schema ID value keys without inline attributes', 'add'
832
+ it_behaves_like 'it handles schema ID value keys without inline attributes', 'replace'
833
+
834
+ shared_examples 'it handles schema ID value keys with inline attributes' do
835
+ it "and performs operation" do
836
+ payload = {
837
+ Operations: [
838
+ {
839
+ op: 'add',
840
+ value: {
841
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization' => 'Foo Bar!',
842
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department' => 'Bar Foo!'
843
+ },
844
+ },
845
+ ]
846
+ }
847
+
848
+ @u2.update!(organization: 'Old org')
849
+ payload = spec_helper_hupcase(payload) if force_upper_case
850
+ patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
851
+
852
+ expect(response.status ).to eql(200)
853
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
854
+
855
+ @u2.reload
856
+ result = JSON.parse(response.body)
857
+
858
+ expect(@u2.organization).to eql('Foo Bar!')
859
+ expect(@u2.department ).to eql('Bar Foo!')
860
+ end
861
+ end
862
+
863
+ it_behaves_like 'it handles schema ID value keys with inline attributes', 'add'
864
+ it_behaves_like 'it handles schema ID value keys with inline attributes', 'replace'
865
+ end
866
+
767
867
  it 'which patches "returned: \'never\'" fields' do
768
868
  payload = {
769
869
  Operations: [
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: 2.7.2
4
+ version: 2.7.3
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: 2024-03-27 00:00:00.000000000 Z
12
+ date: 2024-06-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails