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.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +23 -98
- data/app/controllers/scimitar/application_controller.rb +13 -41
- data/app/controllers/scimitar/resource_types_controller.rb +2 -0
- data/app/controllers/scimitar/resources_controller.rb +2 -0
- data/app/controllers/scimitar/schemas_controller.rb +3 -366
- data/app/controllers/scimitar/service_provider_configurations_controller.rb +1 -0
- data/app/models/scimitar/complex_types/address.rb +6 -0
- data/app/models/scimitar/engine_configuration.rb +5 -15
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/lists/query_parser.rb +13 -113
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resources/base.rb +9 -53
- data/app/models/scimitar/resources/mixin.rb +59 -646
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +5 -14
- data/app/models/scimitar/schema/base.rb +1 -1
- data/app/models/scimitar/schema/name.rb +2 -2
- data/app/models/scimitar/schema/user.rb +10 -10
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/app/models/scimitar/service_provider_configuration.rb +3 -14
- data/config/initializers/scimitar.rb +3 -69
- data/lib/scimitar/engine.rb +12 -57
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +2 -7
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
- data/spec/apps/dummy/app/models/mock_group.rb +1 -1
- data/spec/apps/dummy/app/models/mock_user.rb +9 -52
- 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 -90
- data/spec/apps/dummy/config/routes.rb +7 -28
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -11
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
- data/spec/apps/dummy/db/schema.rb +4 -12
- data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +48 -344
- data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -146
- data/spec/models/scimitar/resources/base_spec.rb +71 -217
- data/spec/models/scimitar/resources/base_validation_spec.rb +5 -43
- data/spec/models/scimitar/resources/mixin_spec.rb +129 -1508
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +2 -12
- data/spec/requests/active_record_backed_resources_controller_spec.rb +66 -1016
- data/spec/requests/application_controller_spec.rb +3 -16
- data/spec/requests/engine_spec.rb +0 -75
- data/spec/spec_helper.rb +1 -9
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
- metadata +26 -37
- data/LICENSE.txt +0 -21
- data/README.md +0 -717
- data/lib/scimitar/support/utilities.rb +0 -111
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
- data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -139,31 +139,27 @@ module Scimitar
|
|
139
139
|
# # ...
|
140
140
|
# groups: [
|
141
141
|
# {
|
142
|
-
# list:
|
142
|
+
# list: :users, # <-- i.e. Team.users,
|
143
143
|
# using: {
|
144
144
|
# value: :id, # <-- i.e. Team.users[n].id
|
145
145
|
# display: :full_name # <-- i.e. Team.users[n].full_name
|
146
146
|
# },
|
147
|
-
# class: Team, # Optional; see below
|
148
147
|
# find_with: -> (scim_list_entry) {...} # See below
|
149
148
|
# }
|
150
149
|
# ],
|
151
150
|
# #...
|
152
151
|
# end
|
153
152
|
#
|
154
|
-
# The mixing-in class
|
153
|
+
# The mixing-in class _must+ implement the read accessor identified by the
|
155
154
|
# value of the "list" key, returning any indexed, Enumerable collection
|
156
155
|
# (e.g. an Array or ActiveRecord::Relation instance). The optional key
|
157
|
-
# ":find_with" is defined with a Proc that
|
156
|
+
# ":find_with" is defined with a Proc that's passed the SCIM entry at each
|
158
157
|
# list position. It must use this to look up the equivalent entry for
|
159
158
|
# association via the write accessor described by the ":list" key. In the
|
160
159
|
# example above, "find_with"'s Proc might look at a SCIM entry value which
|
161
160
|
# is expected to be a user ID and find that User. The mapped set of User
|
162
161
|
# data thus found would be written back with "#users=", due to the ":list"
|
163
|
-
# key declaring the method name ":users".
|
164
|
-
# recommended but not really *needed* unless the configuration option
|
165
|
-
# Scimitar::EngineConfiguration::schema_list_from_attribute_mappings is
|
166
|
-
# defined; see documentation of that option for more information.
|
162
|
+
# key declaring the method name ":users".
|
167
163
|
#
|
168
164
|
# Note that you can only use either:
|
169
165
|
#
|
@@ -180,8 +176,7 @@ module Scimitar
|
|
180
176
|
# == scim_mutable_attributes
|
181
177
|
#
|
182
178
|
# Define this method to return a Set (preferred) or Array of names of
|
183
|
-
# attributes which may be written in the mixing-in class.
|
184
|
-
# expressed as Symbols, *not* Strings.
|
179
|
+
# attributes which may be written in the mixing-in class.
|
185
180
|
#
|
186
181
|
# If you return +nil+, it is assumed that +any+ attribute mapped by
|
187
182
|
# ::scim_attributes_map which has a write accessor will be eligible for
|
@@ -209,12 +204,12 @@ module Scimitar
|
|
209
204
|
# Define this method to return a Hash that maps field names you wish to
|
210
205
|
# support in SCIM filter queries to corresponding attributes in the in the
|
211
206
|
# mixing-in class. If +nil+ then filtering is not supported in the
|
212
|
-
#
|
207
|
+
# ResouceController subclass which declares that it maps to the mixing-in
|
213
208
|
# class. If not +nil+ but a SCIM filter enquiry is made for an unmapped
|
214
209
|
# attribute, an 'invalid filter' exception is raised.
|
215
210
|
#
|
216
211
|
# If using ActiveRecord support in Scimitar::Lists::QueryParser, the mapped
|
217
|
-
#
|
212
|
+
# entites are columns and that's expressed in the names of keys described
|
218
213
|
# below; if you have other approaches to searching, these might be virtual
|
219
214
|
# attributes or other such constructs rather than columns. That would be up
|
220
215
|
# to your non-ActiveRecord's implementation to decide.
|
@@ -225,8 +220,13 @@ module Scimitar
|
|
225
220
|
# allow for different client searching "styles", given ambiguities in RFC
|
226
221
|
# 7644 filter examples).
|
227
222
|
#
|
228
|
-
# Each value is a
|
229
|
-
#
|
223
|
+
# Each value is a Hash with Symbol keys ':column', naming just one simple
|
224
|
+
# column for a mapping; ':columns', with an Array of column names that you
|
225
|
+
# want to map using 'OR' for a single search on the corresponding SCIM
|
226
|
+
# attribute; or ':ignore' with value 'true', which means that a fitler on
|
227
|
+
# the matching attribute is ignored rather than resulting in an "invalid
|
228
|
+
# filter" exception - beware possibilities for surprised clients getting a
|
229
|
+
# broader result set than expected. Example:
|
230
230
|
#
|
231
231
|
# def self.scim_queryable_attributes
|
232
232
|
# return {
|
@@ -234,27 +234,10 @@ module Scimitar
|
|
234
234
|
# 'name.familyName' => { column: :last_name },
|
235
235
|
# 'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
236
236
|
# 'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
237
|
-
# 'emails.type' => { ignore: true }
|
238
|
-
# 'groups.value' => { column: Group.arel_table[:id] }
|
237
|
+
# 'emails.type' => { ignore: true }
|
239
238
|
# }
|
240
239
|
# end
|
241
240
|
#
|
242
|
-
# Column references can be either a Symbol representing a column within
|
243
|
-
# the resource model table, or an <tt>Arel::Attribute</tt> instance via
|
244
|
-
# e.g. <tt>MyModel.arel_table[:my_column]</tt>.
|
245
|
-
#
|
246
|
-
# === Queryable SCIM attribute options
|
247
|
-
#
|
248
|
-
# +:column+:: Just one simple column for a mapping.
|
249
|
-
#
|
250
|
-
# +:columns+:: An Array of columns that you want to map using 'OR' for a
|
251
|
-
# single search of the corresponding entity.
|
252
|
-
#
|
253
|
-
# +:ignore+:: When set to +true+, the matching attribute is ignored rather
|
254
|
-
# than resulting in an "invalid filter" exception. Beware
|
255
|
-
# possibilities for surprised clients getting a broader result
|
256
|
-
# set than expected, since a constraint may have been ignored.
|
257
|
-
#
|
258
241
|
# Filtering is currently limited and searching within e.g. arrays of data
|
259
242
|
# is not supported; only simple top-level keys can be mapped.
|
260
243
|
#
|
@@ -267,8 +250,8 @@ module Scimitar
|
|
267
250
|
# both of the keys 'created' and 'lastModified', as Symbols. The values
|
268
251
|
# should be methods that the including method supports which return a
|
269
252
|
# creation or most-recently-updated time, respectively. The returned object
|
270
|
-
#
|
271
|
-
#
|
253
|
+
# mustsupport #iso8601 to convert to a String representation. Example for a
|
254
|
+
# typical ActiveRecord object with standard timestamps:
|
272
255
|
#
|
273
256
|
# def self.scim_timestamps_map
|
274
257
|
# {
|
@@ -296,7 +279,7 @@ module Scimitar
|
|
296
279
|
# the result in an instance variable.
|
297
280
|
#
|
298
281
|
def scim_mutable_attributes
|
299
|
-
@scim_mutable_attributes ||= self.class.scim_mutable_attributes()
|
282
|
+
@scim_mutable_attributes ||= self.class.scim_mutable_attributes()
|
300
283
|
|
301
284
|
if @scim_mutable_attributes.nil?
|
302
285
|
@scim_mutable_attributes = Set.new
|
@@ -340,36 +323,18 @@ module Scimitar
|
|
340
323
|
@scim_queryable_attributes ||= self.class.scim_queryable_attributes()
|
341
324
|
end
|
342
325
|
|
343
|
-
# Render self as a SCIM object using ::scim_attributes_map.
|
344
|
-
# are marked as <tt>returned: 'never'</tt> are excluded.
|
326
|
+
# Render self as a SCIM object using ::scim_attributes_map.
|
345
327
|
#
|
346
|
-
# +location+::
|
347
|
-
#
|
348
|
-
#
|
349
|
-
#
|
350
|
-
# to generate this.
|
328
|
+
# +location+:: The location (HTTP(S) full URI) of this resource, in the
|
329
|
+
# domain of the object including this mixin - "your" IDs,
|
330
|
+
# not the remote SCIM client's external IDs. #url_for is a
|
331
|
+
# good way to generate this.
|
351
332
|
#
|
352
|
-
|
353
|
-
# response, in the form of a list of full
|
354
|
-
# attribute paths. Schema IDs are not supported.
|
355
|
-
# See RFC 7644 section 3.9 and section 3.10 for
|
356
|
-
# more. When a collection is given, +nil+ value
|
357
|
-
# items are also excluded from the response. If
|
358
|
-
# omitted or given an empty collection, all
|
359
|
-
# attributes are included.
|
360
|
-
#
|
361
|
-
def to_scim(location:, include_attributes: [])
|
333
|
+
def to_scim(location:)
|
362
334
|
map = self.class.scim_attributes_map()
|
363
|
-
resource_type = self.class.scim_resource_type()
|
364
335
|
timestamps_map = self.class.scim_timestamps_map() if self.class.respond_to?(:scim_timestamps_map)
|
365
|
-
attrs_hash = self.to_scim_backend(
|
366
|
-
|
367
|
-
resource_type: resource_type,
|
368
|
-
attrs_map_or_leaf_value: map,
|
369
|
-
include_attributes: include_attributes
|
370
|
-
)
|
371
|
-
|
372
|
-
resource = resource_type.new(attrs_hash)
|
336
|
+
attrs_hash = self.to_scim_backend(data_source: self, attrs_map_or_leaf_value: map)
|
337
|
+
resource = self.class.scim_resource_type().new(attrs_hash)
|
373
338
|
meta_attrs_hash = { location: location }
|
374
339
|
|
375
340
|
meta_attrs_hash[:created ] = self.send(timestamps_map[:created ])&.iso8601(0) if timestamps_map&.key?(:created)
|
@@ -396,39 +361,16 @@ module Scimitar
|
|
396
361
|
#
|
397
362
|
# Call ONLY for POST or PUT. For PATCH, see #from_scim_patch!.
|
398
363
|
#
|
399
|
-
#
|
400
|
-
#
|
401
|
-
# +scim_hash+:: A Hash that's the result of parsing a JSON payload
|
402
|
-
# from an inbound POST or PUT request.
|
403
|
-
#
|
404
|
-
# Optional named parameters:
|
405
|
-
#
|
406
|
-
# +with_clearing+:: According to RFC 7644 section 3.5.1, PUT operations
|
407
|
-
# MAY default or clear any attribute missing from
|
408
|
-
# +scim_hash+ as this is deemed "not asserted by the
|
409
|
-
# client" (see
|
410
|
-
# https://tools.ietf.org/html/rfc7644#section-3.5.1).
|
411
|
-
# This parameter controls such behaviour. It defaults
|
412
|
-
# to +true+, so clearing is applied - single value
|
413
|
-
# attributes are set to +nil+ and arrays are emptied.
|
414
|
-
# If +false+, an unusual <b>preservation</b> mode is
|
415
|
-
# applied and anything absent from +scim_hash+ will
|
416
|
-
# have no impact on the target object (any mapped
|
417
|
-
# attributes in the local data model with existing
|
418
|
-
# non-nil values will retain those values).
|
364
|
+
# +scim_hash+:: A Hash that's the result of parsing a JSON payload
|
365
|
+
# from an inbound POST or PUT request.
|
419
366
|
#
|
420
367
|
# Returns 'self', for convenience of e.g. chaining other methods.
|
421
368
|
#
|
422
|
-
def from_scim!(scim_hash
|
369
|
+
def from_scim!(scim_hash:)
|
423
370
|
scim_hash.freeze()
|
424
371
|
map = self.class.scim_attributes_map().freeze()
|
425
372
|
|
426
|
-
self.from_scim_backend!(
|
427
|
-
attrs_map_or_leaf_value: map,
|
428
|
-
scim_hash_or_leaf_value: scim_hash,
|
429
|
-
with_clearing: with_clearing
|
430
|
-
)
|
431
|
-
|
373
|
+
self.from_scim_backend!(attrs_map_or_leaf_value: map, scim_hash_or_leaf_value: scim_hash)
|
432
374
|
return self
|
433
375
|
end
|
434
376
|
|
@@ -464,11 +406,8 @@ module Scimitar
|
|
464
406
|
def from_scim_patch!(patch_hash:)
|
465
407
|
frozen_ci_patch_hash = patch_hash.with_indifferent_case_insensitive_access().freeze()
|
466
408
|
ci_scim_hash = self.to_scim(location: '(unused)').as_json().with_indifferent_case_insensitive_access()
|
467
|
-
operations = frozen_ci_patch_hash['operations']
|
468
409
|
|
469
|
-
|
470
|
-
|
471
|
-
operations.each do |operation|
|
410
|
+
frozen_ci_patch_hash['operations'].each do |operation|
|
472
411
|
nature = operation['op' ]&.downcase
|
473
412
|
path_str = operation['path' ]
|
474
413
|
value = operation['value']
|
@@ -504,21 +443,11 @@ module Scimitar
|
|
504
443
|
ci_scim_hash = { 'root' => ci_scim_hash }.with_indifferent_case_insensitive_access()
|
505
444
|
end
|
506
445
|
|
507
|
-
# Split the path into an array of path components, in a way
|
508
|
-
# which is aware of extension schemas. See documentation of
|
509
|
-
# Scimitar::Support::Utilities.path_str_to_array for details.
|
510
|
-
#
|
511
|
-
paths = ::Scimitar::Support::Utilities.path_str_to_array(
|
512
|
-
self.class.scim_resource_type.extended_schemas,
|
513
|
-
path_str
|
514
|
-
)
|
515
|
-
|
516
446
|
self.from_patch_backend!(
|
517
447
|
nature: nature,
|
518
|
-
path:
|
448
|
+
path: (path_str || '').split('.'),
|
519
449
|
value: value,
|
520
|
-
altering_hash: ci_scim_hash
|
521
|
-
with_attr_map: self.class.scim_attributes_map()
|
450
|
+
altering_hash: ci_scim_hash
|
522
451
|
)
|
523
452
|
|
524
453
|
if extract_root
|
@@ -526,7 +455,7 @@ module Scimitar
|
|
526
455
|
end
|
527
456
|
end
|
528
457
|
|
529
|
-
self.from_scim!(scim_hash: ci_scim_hash
|
458
|
+
self.from_scim!(scim_hash: ci_scim_hash)
|
530
459
|
return self
|
531
460
|
end
|
532
461
|
|
@@ -544,71 +473,16 @@ module Scimitar
|
|
544
473
|
# this is "self" (an instance of the
|
545
474
|
# class mixing in this module).
|
546
475
|
#
|
547
|
-
# +resource_type+:: The resource type carrying the schemas
|
548
|
-
# describing the SCIM object. If at the
|
549
|
-
# top level when +data_source+ is +self+,
|
550
|
-
# this would be sent as
|
551
|
-
# <tt>self.class.scim_resource_type()</tt>.
|
552
|
-
#
|
553
476
|
# +attrs_map_or_leaf_value+:: The attribute map. At the top level,
|
554
477
|
# this is from ::scim_attributes_map.
|
555
478
|
#
|
556
|
-
|
557
|
-
# in the response, in the form of a list
|
558
|
-
# of full attribute paths. Schema IDs are
|
559
|
-
# not supported. See RFC 7644 section
|
560
|
-
# 3.9 and section 3.10 for more. When a
|
561
|
-
# collection is given, +nil+ value items
|
562
|
-
# are also excluded from the response. If
|
563
|
-
# omitted or given an empty collection,
|
564
|
-
# all attributes are included.
|
565
|
-
#
|
566
|
-
# Internal recursive calls also send:
|
567
|
-
#
|
568
|
-
# +attribute_path+:: Array of path components to the
|
569
|
-
# attribute, which can be found through
|
570
|
-
# +resource_type+ so that things like the
|
571
|
-
# "+returned+" state can be checked.
|
572
|
-
#
|
573
|
-
def to_scim_backend(
|
574
|
-
data_source:,
|
575
|
-
resource_type:,
|
576
|
-
attrs_map_or_leaf_value:,
|
577
|
-
include_attributes:,
|
578
|
-
attribute_path: []
|
579
|
-
)
|
580
|
-
# NOTE EARLY EXIT
|
581
|
-
#
|
582
|
-
return unless scim_attribute_included?(
|
583
|
-
include_attributes: include_attributes,
|
584
|
-
attribute_path: attribute_path
|
585
|
-
)
|
586
|
-
|
587
|
-
# On assumption of a top-level attributes list, the 'return never'
|
588
|
-
# state is only checked on the recursive call from a Hash type. The
|
589
|
-
# other handled types are assumed to only happen when called
|
590
|
-
# recursively, so no need to check as no such call is made for a
|
591
|
-
# 'return never' attribute.
|
592
|
-
#
|
479
|
+
def to_scim_backend(data_source:, attrs_map_or_leaf_value:)
|
593
480
|
case attrs_map_or_leaf_value
|
594
481
|
when Hash # Expected at top-level of any map, or nested within
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
if resource_type.find_attribute(*nested_attribute_path)&.returned != "never"
|
599
|
-
hash[key] = to_scim_backend(
|
600
|
-
data_source: data_source,
|
601
|
-
resource_type: resource_type,
|
602
|
-
attribute_path: nested_attribute_path,
|
603
|
-
attrs_map_or_leaf_value: value,
|
604
|
-
include_attributes: include_attributes
|
605
|
-
)
|
606
|
-
end
|
482
|
+
attrs_map_or_leaf_value.each.with_object({}) do |(key, value), hash|
|
483
|
+
hash[key] = to_scim_backend(data_source: data_source, attrs_map_or_leaf_value: value)
|
607
484
|
end
|
608
485
|
|
609
|
-
result.compact! if include_attributes.any?
|
610
|
-
result
|
611
|
-
|
612
486
|
when Array # Static or dynamic mapping against lists in data source
|
613
487
|
built_dynamic_list = false
|
614
488
|
mapped_array = attrs_map_or_leaf_value.map do |value|
|
@@ -617,28 +491,14 @@ module Scimitar
|
|
617
491
|
|
618
492
|
elsif value.key?(:match) # Static map
|
619
493
|
static_hash = { value[:match] => value[:with] }
|
620
|
-
static_hash.merge!(
|
621
|
-
to_scim_backend(
|
622
|
-
data_source: data_source,
|
623
|
-
resource_type: resource_type,
|
624
|
-
attribute_path: attribute_path,
|
625
|
-
attrs_map_or_leaf_value: value[:using],
|
626
|
-
include_attributes: include_attributes
|
627
|
-
)
|
628
|
-
)
|
494
|
+
static_hash.merge!(to_scim_backend(data_source: data_source, attrs_map_or_leaf_value: value[:using]))
|
629
495
|
static_hash
|
630
496
|
|
631
497
|
elsif value.key?(:list) # Dynamic mapping of each complex list item
|
632
498
|
built_dynamic_list = true
|
633
499
|
list = data_source.public_send(value[:list])
|
634
500
|
list.map do |list_entry|
|
635
|
-
to_scim_backend(
|
636
|
-
data_source: list_entry,
|
637
|
-
resource_type: resource_type,
|
638
|
-
attribute_path: attribute_path,
|
639
|
-
attrs_map_or_leaf_value: value[:using],
|
640
|
-
include_attributes: include_attributes
|
641
|
-
)
|
501
|
+
to_scim_backend(data_source: list_entry, attrs_map_or_leaf_value: value[:using])
|
642
502
|
end
|
643
503
|
|
644
504
|
else # Unknown type, just treat as flat values
|
@@ -729,15 +589,6 @@ module Scimitar
|
|
729
589
|
# read as input source material (left
|
730
590
|
# hand side of the ASCII art diagram).
|
731
591
|
#
|
732
|
-
# +with_clearing+:: If +true+, attributes absent in
|
733
|
-
# +scim_hash_or_leaf_value+ but present
|
734
|
-
# in +attrs_map_or_leaf_value+ will be
|
735
|
-
# cleared (+nil+ or empty array), for PUT
|
736
|
-
# ("replace") semantics. If +false+, such
|
737
|
-
# missing attribute values are left
|
738
|
-
# untouched - whatever mapped value is in
|
739
|
-
# +self+ is preserved.
|
740
|
-
#
|
741
592
|
# +path+:: Array of SCIM attribute names giving a
|
742
593
|
# path into the SCIM schema where
|
743
594
|
# iteration has reached. Used to find the
|
@@ -747,7 +598,6 @@ module Scimitar
|
|
747
598
|
def from_scim_backend!(
|
748
599
|
attrs_map_or_leaf_value:,
|
749
600
|
scim_hash_or_leaf_value:,
|
750
|
-
with_clearing:,
|
751
601
|
path: []
|
752
602
|
)
|
753
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)
|
@@ -766,49 +616,13 @@ module Scimitar
|
|
766
616
|
attrs_map_or_leaf_value.each do | scim_attribute, sub_attrs_map_or_leaf_value |
|
767
617
|
next if scim_attribute&.to_s&.downcase == 'id' && path.empty?
|
768
618
|
|
769
|
-
|
770
|
-
# @MorrisFreeman via:
|
771
|
-
#
|
772
|
-
# https://github.com/RIPAGlobal/scimitar/issues/48
|
773
|
-
# https://github.com/RIPAGlobal/scimitar/pull/49
|
774
|
-
#
|
775
|
-
# Note the shortcoming that attribute names within extensions
|
776
|
-
# must be unique, as this mechanism basically just pulls out
|
777
|
-
# extension attributes to the top level, losing what amounts
|
778
|
-
# to the namespace that the extension schema ID provides.
|
779
|
-
#
|
780
|
-
attribute_tree = []
|
781
|
-
resource_class.extended_schemas.each do |schema|
|
782
|
-
if schema.scim_attributes.any? { |attribute| attribute.name == scim_attribute.to_s }
|
783
|
-
attribute_tree << schema.id
|
784
|
-
break # NOTE EARLY LOOP EXIT
|
785
|
-
end
|
786
|
-
end
|
787
|
-
attribute_tree << scim_attribute.to_s
|
788
|
-
|
789
|
-
continue_processing = if with_clearing
|
790
|
-
true
|
791
|
-
else
|
792
|
-
most_of_attribute_tree = attribute_tree[...-1]
|
793
|
-
last_attribute_in_tree = attribute_tree.last
|
619
|
+
sub_scim_hash_or_leaf_value = scim_hash_or_leaf_value&.dig(scim_attribute.to_s)
|
794
620
|
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
end
|
801
|
-
|
802
|
-
if continue_processing
|
803
|
-
sub_scim_hash_or_leaf_value = scim_hash_or_leaf_value&.dig(*attribute_tree)
|
804
|
-
|
805
|
-
self.from_scim_backend!(
|
806
|
-
attrs_map_or_leaf_value: sub_attrs_map_or_leaf_value,
|
807
|
-
scim_hash_or_leaf_value: sub_scim_hash_or_leaf_value, # May be 'nil'
|
808
|
-
with_clearing: with_clearing,
|
809
|
-
path: path + [scim_attribute]
|
810
|
-
)
|
811
|
-
end
|
621
|
+
self.from_scim_backend!(
|
622
|
+
attrs_map_or_leaf_value: sub_attrs_map_or_leaf_value,
|
623
|
+
scim_hash_or_leaf_value: sub_scim_hash_or_leaf_value, # May be 'nil'
|
624
|
+
path: path + [scim_attribute]
|
625
|
+
)
|
812
626
|
end
|
813
627
|
|
814
628
|
when Array # Static or dynamic maps
|
@@ -830,7 +644,6 @@ module Scimitar
|
|
830
644
|
self.from_scim_backend!(
|
831
645
|
attrs_map_or_leaf_value: sub_attrs_map,
|
832
646
|
scim_hash_or_leaf_value: found_source_list_entry, # May be 'nil'
|
833
|
-
with_clearing: with_clearing,
|
834
647
|
path: path
|
835
648
|
)
|
836
649
|
|
@@ -886,9 +699,7 @@ module Scimitar
|
|
886
699
|
# +path+:: Operation path, as a series of array entries (so
|
887
700
|
# an inbound dot-separated path string would first
|
888
701
|
# be split into an array by the caller). For
|
889
|
-
# internal recursive calls, this will
|
890
|
-
# of array entries from an index somewhere into the
|
891
|
-
# top-level array, through to its end.
|
702
|
+
# internal recursive calls, this will
|
892
703
|
#
|
893
704
|
# +value+:: The value to apply at the attribute(s) identified
|
894
705
|
# by +path+. Ignored for 'remove' operations.
|
@@ -904,7 +715,7 @@ module Scimitar
|
|
904
715
|
# own wrapping Hash with a single key addressing the SCIM object of
|
905
716
|
# interest and supply this key as the sole array entry in +path+.
|
906
717
|
#
|
907
|
-
def from_patch_backend!(nature:, path:, value:, altering_hash
|
718
|
+
def from_patch_backend!(nature:, path:, value:, altering_hash:)
|
908
719
|
raise 'Case sensitivity violation' unless altering_hash.is_a?(Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess)
|
909
720
|
|
910
721
|
# These all throw exceptions if data is not as expected / required,
|
@@ -915,16 +726,14 @@ module Scimitar
|
|
915
726
|
nature: nature,
|
916
727
|
path: path,
|
917
728
|
value: value,
|
918
|
-
altering_hash: altering_hash
|
919
|
-
with_attr_map: with_attr_map
|
729
|
+
altering_hash: altering_hash
|
920
730
|
)
|
921
731
|
else
|
922
732
|
from_patch_backend_traverse!(
|
923
733
|
nature: nature,
|
924
734
|
path: path,
|
925
735
|
value: value,
|
926
|
-
altering_hash: altering_hash
|
927
|
-
with_attr_map: with_attr_map
|
736
|
+
altering_hash: altering_hash
|
928
737
|
)
|
929
738
|
end
|
930
739
|
|
@@ -944,7 +753,7 @@ module Scimitar
|
|
944
753
|
#
|
945
754
|
# Happily throws exceptions if data is not as expected / required.
|
946
755
|
#
|
947
|
-
def from_patch_backend_traverse!(nature:, path:, value:, altering_hash
|
756
|
+
def from_patch_backend_traverse!(nature:, path:, value:, altering_hash:)
|
948
757
|
raise 'Case sensitivity violation' unless altering_hash.is_a?(Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess)
|
949
758
|
|
950
759
|
path_component, filter = extract_filter_from(path_component: path.first)
|
@@ -990,27 +799,11 @@ module Scimitar
|
|
990
799
|
end
|
991
800
|
|
992
801
|
found_data_for_recursion.each do | found_data |
|
993
|
-
attr_map = if path_component.to_sym == :root
|
994
|
-
with_attr_map
|
995
|
-
else
|
996
|
-
with_attr_map[path_component.to_sym]
|
997
|
-
end
|
998
|
-
|
999
|
-
# Static array mappings need us to find the right map entry that
|
1000
|
-
# corresponds to the SCIM data at hand and recurse back into the
|
1001
|
-
# patch engine with the ":using" attribute map data.
|
1002
|
-
#
|
1003
|
-
if attr_map.is_a?(Array)
|
1004
|
-
array_attr_map = find_matching_static_attr_map(data: found_data, with_attr_map: attr_map)
|
1005
|
-
attr_map = array_attr_map unless array_attr_map.nil?
|
1006
|
-
end
|
1007
|
-
|
1008
802
|
self.from_patch_backend!(
|
1009
803
|
nature: nature,
|
1010
|
-
path: path[1
|
804
|
+
path: path[1..-1],
|
1011
805
|
value: value,
|
1012
|
-
altering_hash: found_data
|
1013
|
-
with_attr_map: attr_map
|
806
|
+
altering_hash: found_data
|
1014
807
|
)
|
1015
808
|
end
|
1016
809
|
end
|
@@ -1025,7 +818,7 @@ module Scimitar
|
|
1025
818
|
#
|
1026
819
|
# Happily throws exceptions if data is not as expected / required.
|
1027
820
|
#
|
1028
|
-
def from_patch_backend_apply!(nature:, path:, value:, altering_hash
|
821
|
+
def from_patch_backend_apply!(nature:, path:, value:, altering_hash:)
|
1029
822
|
raise 'Case sensitivity violation' unless altering_hash.is_a?(Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess)
|
1030
823
|
|
1031
824
|
path_component, filter = extract_filter_from(path_component: path.first)
|
@@ -1055,48 +848,11 @@ module Scimitar
|
|
1055
848
|
|
1056
849
|
case nature
|
1057
850
|
when 'remove'
|
1058
|
-
|
1059
|
-
|
1060
|
-
attr_map_entry = with_attr_map.dig(*attr_map_path.map(&:to_sym))
|
1061
|
-
|
1062
|
-
# Deal with arrays specially; static maps require specific
|
1063
|
-
# treatment, but dynamic or actual array values do not.
|
1064
|
-
#
|
1065
|
-
if attr_map_entry.is_a?(Array)
|
1066
|
-
array_attr_map = find_matching_static_attr_map(
|
1067
|
-
data: matched_hash,
|
1068
|
-
with_attr_map: attr_map_entry
|
1069
|
-
)
|
1070
|
-
|
1071
|
-
# Found? Run through the mapped attributes. Anything that
|
1072
|
-
# has an associated model attribute (i.e. some property
|
1073
|
-
# that must be to be written into local data in response
|
1074
|
-
# to the SCIM attribute being changed) is 'removed' by
|
1075
|
-
# setting the corresponding value in "altering_hash" (of
|
1076
|
-
# which "matched_hash" referenced fragment) to "nil".
|
1077
|
-
#
|
1078
|
-
handled = clear_data_for_removal!(
|
1079
|
-
altering_hash: matched_hash,
|
1080
|
-
with_attr_map: array_attr_map
|
1081
|
-
)
|
1082
|
-
end
|
1083
|
-
|
1084
|
-
# For dynamic arrays or other value types, we assume that
|
1085
|
-
# just clearing the item from the array or setting its SCIM
|
1086
|
-
# attribute to "nil" will result in an appropriate update
|
1087
|
-
# to the local data model (e.g. by a change in an Rails
|
1088
|
-
# associated collection or clearing a local model attribute
|
1089
|
-
# directly to "nil").
|
1090
|
-
#
|
1091
|
-
if handled == false
|
1092
|
-
current_data_at_path[matched_index] = nil
|
1093
|
-
compact_after = true
|
1094
|
-
end
|
1095
|
-
|
851
|
+
current_data_at_path[matched_index] = nil
|
852
|
+
compact_after = true
|
1096
853
|
when 'replace'
|
1097
854
|
matched_hash.reject! { true }
|
1098
855
|
matched_hash.merge!(value)
|
1099
|
-
|
1100
856
|
end
|
1101
857
|
end
|
1102
858
|
|
@@ -1135,169 +891,20 @@ module Scimitar
|
|
1135
891
|
# at key 'members' with the above, rather than adding.
|
1136
892
|
#
|
1137
893
|
value.keys.each do | key |
|
1138
|
-
|
1139
|
-
# Handle the Azure (Entra) case where keys might use
|
1140
|
-
# dotted paths - see:
|
1141
|
-
#
|
1142
|
-
# https://github.com/RIPAGlobal/scimitar/issues/123
|
1143
|
-
#
|
1144
|
-
# ...along with keys containing schema IDs - see:
|
1145
|
-
#
|
1146
|
-
# https://is.docs.wso2.com/en/next/apis/scim2-patch-operations/#add-user-attributes
|
1147
|
-
#
|
1148
|
-
# ...and scroll down to example 3 of "Complex singular
|
1149
|
-
# attributes".
|
1150
|
-
#
|
1151
|
-
subpaths = ::Scimitar::Support::Utilities.path_str_to_array(
|
1152
|
-
self.class.scim_resource_type.extended_schemas,
|
1153
|
-
key
|
1154
|
-
)
|
1155
|
-
|
1156
894
|
from_patch_backend!(
|
1157
895
|
nature: nature,
|
1158
|
-
path: path +
|
896
|
+
path: path + [key],
|
1159
897
|
value: value[key],
|
1160
|
-
altering_hash: altering_hash
|
1161
|
-
with_attr_map: with_attr_map
|
898
|
+
altering_hash: altering_hash
|
1162
899
|
)
|
1163
900
|
end
|
1164
901
|
else
|
1165
902
|
altering_hash[path_component] = value
|
1166
903
|
end
|
1167
|
-
|
1168
904
|
when 'replace'
|
1169
|
-
|
1170
|
-
dot_pathed_value = value.inject({}) do |hash, (k, v)|
|
1171
|
-
subpaths = ::Scimitar::Support::Utilities.path_str_to_array(
|
1172
|
-
self.class.scim_resource_type.extended_schemas,
|
1173
|
-
k
|
1174
|
-
)
|
1175
|
-
|
1176
|
-
hash.deep_merge!(::Scimitar::Support::Utilities.dot_path(subpaths, v))
|
1177
|
-
end
|
1178
|
-
|
1179
|
-
altering_hash[path_component].deep_merge!(dot_pathed_value)
|
1180
|
-
else
|
1181
|
-
altering_hash[path_component] = value
|
1182
|
-
end
|
1183
|
-
|
1184
|
-
# The array check handles payloads seen from e.g. Microsoft for
|
1185
|
-
# remove-user-from-group, where contrary to examples in the RFC
|
1186
|
-
# which would imply "payload removes all users", there is the
|
1187
|
-
# clear intent to remove just one.
|
1188
|
-
#
|
1189
|
-
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
1190
|
-
# https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
|
1191
|
-
#
|
1192
|
-
# Since remove-all in the face of remove-one is destructive, we
|
1193
|
-
# do a special check here to see if there's an array value for
|
1194
|
-
# the array path that the payload yielded. If so, we can match
|
1195
|
-
# each value against array items and remove just those items.
|
1196
|
-
#
|
1197
|
-
# There is an additional special case to handle a bad example
|
1198
|
-
# from Salesforce:
|
1199
|
-
#
|
1200
|
-
# https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
|
1201
|
-
#
|
905
|
+
altering_hash[path_component] = value
|
1202
906
|
when 'remove'
|
1203
|
-
|
1204
|
-
|
1205
|
-
# Handle bad Salesforce example. That might be simply a
|
1206
|
-
# documentation error, but just in case...
|
1207
|
-
#
|
1208
|
-
value = value.values.first if (
|
1209
|
-
path_component&.downcase == 'members' &&
|
1210
|
-
value.is_a?(Hash) &&
|
1211
|
-
value.keys.size == 1 &&
|
1212
|
-
value.keys.first&.downcase == 'members'
|
1213
|
-
)
|
1214
|
-
|
1215
|
-
# The Microsoft example provides an array of values, but we
|
1216
|
-
# may as well cope with a value specified 'flat'. Promote
|
1217
|
-
# such a thing to an Array to simplify the following code.
|
1218
|
-
#
|
1219
|
-
value = [value] unless value.is_a?(Array)
|
1220
|
-
|
1221
|
-
# For each value item, delete matching array entries. The
|
1222
|
-
# concept of "matching" is:
|
1223
|
-
#
|
1224
|
-
# * For simple non-Hash values (if possible) just delete on
|
1225
|
-
# an exact match
|
1226
|
-
#
|
1227
|
-
# * For Hash-based values, only delete if all 'patch' keys
|
1228
|
-
# are present in the resource and all values thus match.
|
1229
|
-
#
|
1230
|
-
# Special case to ignore '$ref' from the Microsoft payload.
|
1231
|
-
#
|
1232
|
-
# Note coercion to strings to account for SCIM vs the usual
|
1233
|
-
# tricky case of underlying implementations with (say)
|
1234
|
-
# integer primary keys, which all end up as strings anyway.
|
1235
|
-
#
|
1236
|
-
value.each do | value_item |
|
1237
|
-
altering_hash[path_component].map! do | item |
|
1238
|
-
item_is_matched = if item.is_a?(Hash) && value_item.is_a?(Hash)
|
1239
|
-
matched_all = true
|
1240
|
-
value_item.each do | value_key, value_value |
|
1241
|
-
next if value_key == '$ref'
|
1242
|
-
if ! item.key?(value_key) || item[value_key]&.to_s != value_value&.to_s
|
1243
|
-
matched_all = false
|
1244
|
-
end
|
1245
|
-
end
|
1246
|
-
matched_all
|
1247
|
-
else
|
1248
|
-
item&.to_s == value_item&.to_s
|
1249
|
-
end
|
1250
|
-
|
1251
|
-
if item_is_matched
|
1252
|
-
handled = false
|
1253
|
-
attr_map_path = path[..-2] + [path_component]
|
1254
|
-
attr_map_entry = with_attr_map.dig(*attr_map_path.map(&:to_sym))
|
1255
|
-
array_attr_map = find_matching_static_attr_map(
|
1256
|
-
data: item,
|
1257
|
-
with_attr_map: attr_map_entry
|
1258
|
-
)
|
1259
|
-
|
1260
|
-
handled = clear_data_for_removal!(
|
1261
|
-
altering_hash: item,
|
1262
|
-
with_attr_map: array_attr_map
|
1263
|
-
)
|
1264
|
-
|
1265
|
-
handled ? item : nil
|
1266
|
-
else
|
1267
|
-
item
|
1268
|
-
end
|
1269
|
-
end
|
1270
|
-
|
1271
|
-
altering_hash[path_component].compact!
|
1272
|
-
end
|
1273
|
-
|
1274
|
-
elsif altering_hash[path_component].is_a?(Array)
|
1275
|
-
handled = false
|
1276
|
-
attr_map_path = path[..-2] + [path_component]
|
1277
|
-
attr_map_entry = with_attr_map.dig(*attr_map_path.map(&:to_sym))
|
1278
|
-
|
1279
|
-
if attr_map_entry.is_a?(Array) # Array mapping
|
1280
|
-
altering_hash[path_component].each do | data_to_check |
|
1281
|
-
array_attr_map = find_matching_static_attr_map(
|
1282
|
-
data: data_to_check,
|
1283
|
-
with_attr_map: attr_map_entry
|
1284
|
-
)
|
1285
|
-
|
1286
|
-
handled = clear_data_for_removal!(
|
1287
|
-
altering_hash: data_to_check,
|
1288
|
-
with_attr_map: array_attr_map
|
1289
|
-
)
|
1290
|
-
end
|
1291
|
-
end
|
1292
|
-
|
1293
|
-
if handled == false
|
1294
|
-
altering_hash[path_component] = []
|
1295
|
-
end
|
1296
|
-
|
1297
|
-
else
|
1298
|
-
altering_hash[path_component] = nil
|
1299
|
-
end
|
1300
|
-
|
907
|
+
altering_hash.delete(path_component)
|
1301
908
|
end
|
1302
909
|
end
|
1303
910
|
end
|
@@ -1374,200 +981,6 @@ module Scimitar
|
|
1374
981
|
end
|
1375
982
|
end
|
1376
983
|
|
1377
|
-
# Static attribute maps are used where SCIM attributes include some
|
1378
|
-
# kind of array, but it's not an arbitrary collection (dynamic maps
|
1379
|
-
# handle those). Instead, specific matched values inside the SCIM
|
1380
|
-
# data are mapped to specific attributes in the local data model.
|
1381
|
-
#
|
1382
|
-
# A typical example is for e-mails, where the SCIM "type" field in an
|
1383
|
-
# array of e-mail addresses might get mapped to detect specific types
|
1384
|
-
# of address such as "work" and "home", which happen to be stored
|
1385
|
-
# locally in dedicated attributes (e.g. "work_email_address").
|
1386
|
-
#
|
1387
|
-
# During certain processing operations we end up with a set of data
|
1388
|
-
# sent in from some SCIM operation and need to make modifications
|
1389
|
-
# (e.g. for a PATCH) that require the attribute map corresponding to
|
1390
|
-
# each part of the inbound SCIM data to be known. That's where this
|
1391
|
-
# method comes in. Usually, it's not hard to traverse a path of SCIM
|
1392
|
-
# data and dig a corresponding path through the attribute map Hash,
|
1393
|
-
# except for static arrays. There, we need to know which of the
|
1394
|
-
# static map entries matches a piece of SCIM data *from entries* in
|
1395
|
-
# the array of SCIM data corresponding to the static map.
|
1396
|
-
#
|
1397
|
-
# Call here with a piece of SCIM data from an array, along with an
|
1398
|
-
# attribute map fragment that must be the Array containing mappings.
|
1399
|
-
# Static mapping entries from this are compared with the data and if
|
1400
|
-
# a match is found, the sub-attribute map from the static entry's
|
1401
|
-
# <tt>:using</tt> key is returned; else +nil+.
|
1402
|
-
#
|
1403
|
-
# Named parameters are:
|
1404
|
-
#
|
1405
|
-
# +data+:: A SCIM data entry from a SCIM data array which is
|
1406
|
-
# mapped via the data given in the +with_attr_map+
|
1407
|
-
# parameter.
|
1408
|
-
#
|
1409
|
-
# +with_attr_map+:: The attributes map fragment which must be an
|
1410
|
-
# Array of mappings for the corresponding array
|
1411
|
-
# in the SCIM data from which +data+ was drawn.
|
1412
|
-
#
|
1413
|
-
# For example, if SCIM data consisted of:
|
1414
|
-
#
|
1415
|
-
# {
|
1416
|
-
# 'emails' => [
|
1417
|
-
# {
|
1418
|
-
# 'type' => 'work',
|
1419
|
-
# 'value' => 'work_1@test.com'
|
1420
|
-
# },
|
1421
|
-
# {
|
1422
|
-
# 'type' => 'work',
|
1423
|
-
# 'value' => 'work_2@test.com'
|
1424
|
-
# }
|
1425
|
-
# ]
|
1426
|
-
# }
|
1427
|
-
#
|
1428
|
-
# ...which was mapped to the local data model using the following
|
1429
|
-
# attribute map:
|
1430
|
-
#
|
1431
|
-
# {
|
1432
|
-
# emails: [
|
1433
|
-
# { match: 'type', with: 'home', using: { value: :home_email } },
|
1434
|
-
# { match: 'type', with: 'work', using: { value: :work_email } },
|
1435
|
-
# ]
|
1436
|
-
# }
|
1437
|
-
#
|
1438
|
-
# ...then when it came to processing the SCIM 'emails' entry, one of
|
1439
|
-
# the array _entries_ therein would be passed in +data+, while the
|
1440
|
-
# attribute map's <tt>:emails</tt> key's value (the _array_ of map
|
1441
|
-
# data) would be given in <tt>:with_attr_map</tt>. The first SCIM
|
1442
|
-
# array entry matches +work+ so the <tt>:using</tt> part of the map
|
1443
|
-
# for that match would be returned:
|
1444
|
-
#
|
1445
|
-
# { value: :work_email }
|
1446
|
-
#
|
1447
|
-
# If there was a SCIM entry with a type of something unrecognised,
|
1448
|
-
# such as 'holday', then +nil+ would be returned since there is no
|
1449
|
-
# matching attribute map entry.
|
1450
|
-
#
|
1451
|
-
# Note that the <tt>:with_attr_map</tt> array can contain dynamic
|
1452
|
-
# mappings or even be just a simple fixed array - only things that
|
1453
|
-
# "look like" static mapping entries are processed (i.e. Hashes with
|
1454
|
-
# a Symbol key of <tt>:match</tt> present), with the rest ignored.
|
1455
|
-
#
|
1456
|
-
def find_matching_static_attr_map(data:, with_attr_map:)
|
1457
|
-
matched_map = with_attr_map.find do | static_or_dynamic_mapping |
|
1458
|
-
|
1459
|
-
# Only interested in Static Array mappings.
|
1460
|
-
#
|
1461
|
-
if static_or_dynamic_mapping.is_a?(Hash) && static_or_dynamic_mapping.key?(:match)
|
1462
|
-
|
1463
|
-
attr_to_match = static_or_dynamic_mapping[:match].to_s
|
1464
|
-
value_to_match = static_or_dynamic_mapping[:with]
|
1465
|
-
sub_attrs_map = static_or_dynamic_mapping[:using]
|
1466
|
-
|
1467
|
-
# If this mapping refers to the matched data at hand,
|
1468
|
-
# then we can process it further (see later below.
|
1469
|
-
#
|
1470
|
-
found = data[attr_to_match] == value_to_match
|
1471
|
-
|
1472
|
-
# Not found? No static map match perhaps; this could be
|
1473
|
-
# because a filter worked on a value which is fixed in
|
1474
|
-
# the static map. For example, a filter might check for
|
1475
|
-
# emails with "primary true", and the emergence of the
|
1476
|
-
# value for "primary" might not be in the data model -
|
1477
|
-
# it could be a constant declared in the 'using' part
|
1478
|
-
# of a static map. Ugh! Check for that.
|
1479
|
-
#
|
1480
|
-
unless found
|
1481
|
-
sub_attrs_map.each do | scim_attr, model_attr_or_constant |
|
1482
|
-
|
1483
|
-
# Only want constants such as 'true' or 'false'.
|
1484
|
-
#
|
1485
|
-
next if model_attr_or_constant.is_a?(Symbol)
|
1486
|
-
|
1487
|
-
# Does the static value match in the source data?
|
1488
|
-
# E.g. a SCIM attribute :primary with value 'true'.
|
1489
|
-
#
|
1490
|
-
if data[scim_attr] == model_attr_or_constant
|
1491
|
-
found = true
|
1492
|
-
break
|
1493
|
-
end
|
1494
|
-
end
|
1495
|
-
end
|
1496
|
-
|
1497
|
-
found
|
1498
|
-
else
|
1499
|
-
false
|
1500
|
-
end
|
1501
|
-
end
|
1502
|
-
|
1503
|
-
return matched_map&.dig(:using)
|
1504
|
-
end
|
1505
|
-
|
1506
|
-
# Related to #find_matching_static_attr_map - often, the reason to
|
1507
|
-
# find a static array entry related to some inbound SCIM data is for
|
1508
|
-
# a removal operation, where the way to "remove" the data in the
|
1509
|
-
# local data model is to set an attribute to "nil". This means you
|
1510
|
-
# need to know if there is an attribute writer related to the SCIM
|
1511
|
-
# data being removed - and #find_matching_static_attr_map helps.
|
1512
|
-
#
|
1513
|
-
# With that done, you can call here with the hash data to be changed
|
1514
|
-
# and fragment of attribute map that #find_matching_static_attr_map
|
1515
|
-
# (or something like it) found.
|
1516
|
-
#
|
1517
|
-
# +altering_hash+:: The fragment of SCIM data that might be updated
|
1518
|
-
# with +nil+ to ultimately lead to an atttribute
|
1519
|
-
# writer identified through +with_attr_map+ being
|
1520
|
-
# called with that value. This is often the same
|
1521
|
-
# that was passed in the +data+ attribute in a
|
1522
|
-
# prior #find_matching_static_attr_map call.
|
1523
|
-
#
|
1524
|
-
# +with_attr_map:: The map fragment that corresponds exactly to the
|
1525
|
-
# +altering_hash+ data - e.g. the return value of a
|
1526
|
-
# prior #find_matching_static_attr_map call.
|
1527
|
-
#
|
1528
|
-
# Update +altering_hash+ in place if the map finds a relevant local
|
1529
|
-
# data model attribute and returns +true+. If no changes are made,
|
1530
|
-
# returns +false+.
|
1531
|
-
#
|
1532
|
-
def clear_data_for_removal!(altering_hash:, with_attr_map:)
|
1533
|
-
handled = false
|
1534
|
-
|
1535
|
-
with_attr_map&.each do | scim_attr, model_attr_or_constant |
|
1536
|
-
|
1537
|
-
# Only process attribute names, not constants.
|
1538
|
-
#
|
1539
|
-
next unless model_attr_or_constant.is_a?(Symbol)
|
1540
|
-
|
1541
|
-
altering_hash[scim_attr] = nil
|
1542
|
-
handled = true
|
1543
|
-
end
|
1544
|
-
|
1545
|
-
return handled
|
1546
|
-
end
|
1547
|
-
|
1548
|
-
# Related to to_scim_backend, this methods tells whether +attribute_path+
|
1549
|
-
# should be included in the current +include_attributes+. This method
|
1550
|
-
# implements the attributes request from RFC 7644, section 3.9 and 3.10.
|
1551
|
-
#
|
1552
|
-
# +include_attributes+:: The attributes that should be included
|
1553
|
-
# in the response, in the form of a list of
|
1554
|
-
# full attribute paths. See RFC 7644 section
|
1555
|
-
# 3.9 and section 3.10. An empty collection
|
1556
|
-
# will include all attributes.
|
1557
|
-
#
|
1558
|
-
# +attribute_path+:: Array of path components to the attribute,
|
1559
|
-
# e.g. <tt>["name", "givenName"]</tt>.
|
1560
|
-
#
|
1561
|
-
def scim_attribute_included?(include_attributes:, attribute_path:)
|
1562
|
-
return true unless attribute_path.any? && include_attributes.any?
|
1563
|
-
|
1564
|
-
full_path = attribute_path.join(".")
|
1565
|
-
attribute_included = full_path.start_with?(*include_attributes)
|
1566
|
-
will_include_nested = include_attributes.any? { |att| att.start_with?(full_path) }
|
1567
|
-
|
1568
|
-
attribute_included || will_include_nested
|
1569
|
-
end
|
1570
|
-
|
1571
984
|
end # "included do"
|
1572
985
|
end # "module Mixin"
|
1573
986
|
end # "module Resources"
|