scimitar 2.4.0 → 2.4.1
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/models/scimitar/resources/mixin.rb +36 -3
- data/lib/scimitar/version.rb +2 -2
- data/spec/apps/dummy/app/models/mock_user.rb +9 -1
- data/spec/apps/dummy/config/initializers/scimitar.rb +37 -0
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +8 -0
- data/spec/apps/dummy/db/schema.rb +2 -0
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
- data/spec/models/scimitar/resources/base_spec.rb +161 -66
- data/spec/models/scimitar/resources/mixin_spec.rb +84 -5
- data/spec/requests/active_record_backed_resources_controller_spec.rb +1 -1
- metadata +10 -10
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 7db2b533d9bc2fe1e359a12ad51fea89bdd5498589451b16b99987ca0cd99f09
         | 
| 4 | 
            +
              data.tar.gz: 7b30102df8ee36d8bac2698ef150a3537c7b40f6193a64e3baed7e8d6c8386d4
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 238c6bdebc57c0d8c186ad77d8222a79c7f9fad98b86a1d564c2c27d62a58dd809c1a4b2a7a9c57af535728f6a23d36821c12e74be81e8191d96960cdd1d1db9
         | 
| 7 | 
            +
              data.tar.gz: 684c778f5e3d03719b3df872ca8efcb29a18cd802360964282e38ed914b095676aeec4556a625c99f2a3de86f898cae1dd989be67614711194fd176a1c5895c1
         | 
| @@ -443,9 +443,30 @@ module Scimitar | |
| 443 443 | 
             
                          ci_scim_hash = { 'root' => ci_scim_hash }.with_indifferent_case_insensitive_access()
         | 
| 444 444 | 
             
                        end
         | 
| 445 445 |  | 
| 446 | 
            +
                        # Handle extension schema. Contributed by @bettysteger and
         | 
| 447 | 
            +
                        # @MorrisFreeman via:
         | 
| 448 | 
            +
                        #
         | 
| 449 | 
            +
                        #   https://github.com/RIPAGlobal/scimitar/issues/48
         | 
| 450 | 
            +
                        #   https://github.com/RIPAGlobal/scimitar/pull/49
         | 
| 451 | 
            +
                        #
         | 
| 452 | 
            +
                        # Note the ":" separating the schema ID (URN) from the attribute.
         | 
| 453 | 
            +
                        # The nature of JSON rendering / other payloads might lead you to
         | 
| 454 | 
            +
                        # expect a "." as with any complex types, but that's not the case;
         | 
| 455 | 
            +
                        # see https://tools.ietf.org/html/rfc7644#section-3.10, or
         | 
| 456 | 
            +
                        # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
         | 
| 457 | 
            +
                        # particular, https://tools.ietf.org/html/rfc7644#page-35.
         | 
| 458 | 
            +
                        #
         | 
| 459 | 
            +
                        paths = []
         | 
| 460 | 
            +
                        self.class.scim_resource_type.extended_schemas.each do |schema|
         | 
| 461 | 
            +
                          path_str.downcase.split(schema.id.downcase + ':').drop(1).each do |path|
         | 
| 462 | 
            +
                            paths += [schema.id] + path.split('.')
         | 
| 463 | 
            +
                          end
         | 
| 464 | 
            +
                        end
         | 
| 465 | 
            +
                        paths = path_str.split('.') if paths.empty?
         | 
| 466 | 
            +
             | 
| 446 467 | 
             
                        self.from_patch_backend!(
         | 
| 447 468 | 
             
                          nature:        nature,
         | 
| 448 | 
            -
                          path:           | 
| 469 | 
            +
                          path:          paths,
         | 
| 449 470 | 
             
                          value:         value,
         | 
| 450 471 | 
             
                          altering_hash: ci_scim_hash
         | 
| 451 472 | 
             
                        )
         | 
| @@ -616,7 +637,19 @@ module Scimitar | |
| 616 637 | 
             
                            attrs_map_or_leaf_value.each do | scim_attribute, sub_attrs_map_or_leaf_value |
         | 
| 617 638 | 
             
                              next if scim_attribute&.to_s&.downcase == 'id' && path.empty?
         | 
| 618 639 |  | 
| 619 | 
            -
                               | 
| 640 | 
            +
                              # Handle extension schema. Contributed by @bettysteger and
         | 
| 641 | 
            +
                              # @MorrisFreeman via:
         | 
| 642 | 
            +
                              #
         | 
| 643 | 
            +
                              #   https://github.com/RIPAGlobal/scimitar/issues/48
         | 
| 644 | 
            +
                              #   https://github.com/RIPAGlobal/scimitar/pull/49
         | 
| 645 | 
            +
                              #
         | 
| 646 | 
            +
                              attribute_tree = []
         | 
| 647 | 
            +
                              resource_class.extended_schemas.each do |schema|
         | 
| 648 | 
            +
                                attribute_tree << schema.id and break if schema.scim_attributes.any? { |attribute| attribute.name == scim_attribute.to_s }
         | 
| 649 | 
            +
                              end
         | 
| 650 | 
            +
                              attribute_tree << scim_attribute.to_s
         | 
| 651 | 
            +
             | 
| 652 | 
            +
                              sub_scim_hash_or_leaf_value = scim_hash_or_leaf_value&.dig(*attribute_tree)
         | 
| 620 653 |  | 
| 621 654 | 
             
                              self.from_scim_backend!(
         | 
| 622 655 | 
             
                                attrs_map_or_leaf_value: sub_attrs_map_or_leaf_value,
         | 
| @@ -914,7 +947,7 @@ module Scimitar | |
| 914 947 | 
             
                            # which would imply "payload removes all users", there is the
         | 
| 915 948 | 
             
                            # clear intent to remove just one.
         | 
| 916 949 | 
             
                            #
         | 
| 917 | 
            -
                            # https:// | 
| 950 | 
            +
                            # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
         | 
| 918 951 | 
             
                            # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
         | 
| 919 952 | 
             
                            #
         | 
| 920 953 | 
             
                            # Since remove-all in the face of remove-one is destructive, we
         | 
    
        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 = '2.4. | 
| 6 | 
            +
              VERSION = '2.4.1'
         | 
| 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 = '2023- | 
| 11 | 
            +
              DATE = '2023-03-02'
         | 
| 12 12 |  | 
| 13 13 | 
             
            end
         | 
| @@ -15,6 +15,8 @@ class MockUser < ActiveRecord::Base | |
| 15 15 | 
             
                work_email_address
         | 
| 16 16 | 
             
                home_email_address
         | 
| 17 17 | 
             
                work_phone_number
         | 
| 18 | 
            +
                organization
         | 
| 19 | 
            +
                department
         | 
| 18 20 | 
             
              }
         | 
| 19 21 |  | 
| 20 22 | 
             
              has_and_belongs_to_many :mock_groups
         | 
| @@ -84,7 +86,13 @@ class MockUser < ActiveRecord::Base | |
| 84 86 | 
             
                      }
         | 
| 85 87 | 
             
                    }
         | 
| 86 88 | 
             
                  ],
         | 
| 87 | 
            -
                  active: :is_active
         | 
| 89 | 
            +
                  active: :is_active,
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  # Custom extension schema - see configuration in
         | 
| 92 | 
            +
                  # "spec/apps/dummy/config/initializers/scimitar.rb".
         | 
| 93 | 
            +
                  #
         | 
| 94 | 
            +
                  organization: :organization,
         | 
| 95 | 
            +
                  department:   :department
         | 
| 88 96 | 
             
                }
         | 
| 89 97 | 
             
              end
         | 
| 90 98 |  | 
| @@ -1,5 +1,14 @@ | |
| 1 1 | 
             
            # Test app configuration.
         | 
| 2 2 | 
             
            #
         | 
| 3 | 
            +
            # Note that as a result of https://github.com/RIPAGlobal/scimitar/issues/48,
         | 
| 4 | 
            +
            # tests include a custom extension of the core User schema. A shortcoming of
         | 
| 5 | 
            +
            # some of the code from which Scimitar was originally built is that those
         | 
| 6 | 
            +
            # extensions are done with class-level ivars, so it is largely impossible (or
         | 
| 7 | 
            +
            # at least, impractical in tests) to avoid polluting the core class itself
         | 
| 8 | 
            +
            # with the extension.
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # All related schema tests are written with this in mind.
         | 
| 11 | 
            +
            #
         | 
| 3 12 | 
             
            Rails.application.config.to_prepare do
         | 
| 4 13 | 
             
              Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
         | 
| 5 14 |  | 
| @@ -13,4 +22,32 @@ Rails.application.config.to_prepare do | |
| 13 22 | 
             
                end
         | 
| 14 23 |  | 
| 15 24 | 
             
              })
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              module ScimSchemaExtensions
         | 
| 27 | 
            +
                module User
         | 
| 28 | 
            +
                  class Enterprise < Scimitar::Schema::Base
         | 
| 29 | 
            +
                    def initialize(options = {})
         | 
| 30 | 
            +
                      super(
         | 
| 31 | 
            +
                        name:            'ExtendedUser',
         | 
| 32 | 
            +
                        description:     'Enterprise extension for a User',
         | 
| 33 | 
            +
                        id:              self.class.id,
         | 
| 34 | 
            +
                        scim_attributes: self.class.scim_attributes
         | 
| 35 | 
            +
                      )
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def self.id
         | 
| 39 | 
            +
                      'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    def self.scim_attributes
         | 
| 43 | 
            +
                      [
         | 
| 44 | 
            +
                        Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
         | 
| 45 | 
            +
                        Scimitar::Schema::Attribute.new(name: 'department',   type: 'string')
         | 
| 46 | 
            +
                      ]
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              Scimitar::Resources::User.extend_schema ScimSchemaExtensions::User::Enterprise
         | 
| 16 53 | 
             
            end
         | 
| @@ -3,6 +3,8 @@ class CreateMockUsers < ActiveRecord::Migration[6.1] | |
| 3 3 | 
             
                create_table :mock_users, id: :uuid, primary_key: :primary_key do |t|
         | 
| 4 4 | 
             
                  t.timestamps
         | 
| 5 5 |  | 
| 6 | 
            +
                  # Support part of the core schema
         | 
| 7 | 
            +
                  #
         | 
| 6 8 | 
             
                  t.text :scim_uid
         | 
| 7 9 | 
             
                  t.text :username
         | 
| 8 10 | 
             
                  t.text :first_name
         | 
| @@ -10,6 +12,12 @@ class CreateMockUsers < ActiveRecord::Migration[6.1] | |
| 10 12 | 
             
                  t.text :work_email_address
         | 
| 11 13 | 
             
                  t.text :home_email_address
         | 
| 12 14 | 
             
                  t.text :work_phone_number
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # Support the custom extension schema - see configuration in
         | 
| 17 | 
            +
                  # "spec/apps/dummy/config/initializers/scimitar.rb".
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  t.text :organization
         | 
| 20 | 
            +
                  t.text :department
         | 
| 13 21 | 
             
                end
         | 
| 14 22 | 
             
              end
         | 
| 15 23 | 
             
            end
         | 
| @@ -38,6 +38,8 @@ ActiveRecord::Schema[7.0].define(version: 2021_03_08_044214) do | |
| 38 38 | 
             
                t.text "work_email_address"
         | 
| 39 39 | 
             
                t.text "home_email_address"
         | 
| 40 40 | 
             
                t.text "work_phone_number"
         | 
| 41 | 
            +
                t.text "organization"
         | 
| 42 | 
            +
                t.text "department"
         | 
| 41 43 | 
             
              end
         | 
| 42 44 |  | 
| 43 45 | 
             
              add_foreign_key "mock_groups_users", "mock_groups"
         | 
| @@ -14,9 +14,9 @@ RSpec.describe Scimitar::SchemasController do | |
| 14 14 | 
             
                  get :index, params: { format: :scim }
         | 
| 15 15 | 
             
                  expect(response).to be_ok
         | 
| 16 16 | 
             
                  parsed_body = JSON.parse(response.body)
         | 
| 17 | 
            -
                  expect(parsed_body.length).to eql( | 
| 17 | 
            +
                  expect(parsed_body.length).to eql(3)
         | 
| 18 18 | 
             
                  schema_names = parsed_body.map {|schema| schema['name']}
         | 
| 19 | 
            -
                  expect(schema_names).to match_array(['User', 'Group'])
         | 
| 19 | 
            +
                  expect(schema_names).to match_array(['User', 'ExtendedUser', 'Group'])
         | 
| 20 20 | 
             
                end
         | 
| 21 21 |  | 
| 22 22 | 
             
                it 'returns only the User schema when its id is provided' do
         | 
| @@ -250,90 +250,185 @@ RSpec.describe Scimitar::Resources::Base do | |
| 250 250 | 
             
              end # "context 'dynamic setters based on schema' do"
         | 
| 251 251 |  | 
| 252 252 | 
             
              context 'schema extension' do
         | 
| 253 | 
            -
                 | 
| 254 | 
            -
                   | 
| 255 | 
            -
                     | 
| 256 | 
            -
             | 
| 253 | 
            +
                context 'of custom schema' do
         | 
| 254 | 
            +
                  ThirdCustomSchema = Class.new(Scimitar::Schema::Base) do
         | 
| 255 | 
            +
                    def self.id
         | 
| 256 | 
            +
                      'custom-id'
         | 
| 257 | 
            +
                    end
         | 
| 257 258 |  | 
| 258 | 
            -
             | 
| 259 | 
            -
             | 
| 259 | 
            +
                    def self.scim_attributes
         | 
| 260 | 
            +
                      [ Scimitar::Schema::Attribute.new(name: 'name', type: 'string') ]
         | 
| 261 | 
            +
                    end
         | 
| 260 262 | 
             
                  end
         | 
| 261 | 
            -
                end
         | 
| 262 263 |  | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 265 | 
            -
             | 
| 266 | 
            -
             | 
| 264 | 
            +
                  ExtensionSchema = Class.new(Scimitar::Schema::Base) do
         | 
| 265 | 
            +
                    def self.id
         | 
| 266 | 
            +
                      'extension-id'
         | 
| 267 | 
            +
                    end
         | 
| 267 268 |  | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 269 | 
            +
                    def self.scim_attributes
         | 
| 270 | 
            +
                      [ Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true) ]
         | 
| 271 | 
            +
                    end
         | 
| 270 272 | 
             
                  end
         | 
| 271 | 
            -
                end
         | 
| 272 273 |  | 
| 273 | 
            -
             | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 274 | 
            +
                  let(:resource_class) {
         | 
| 275 | 
            +
                    Class.new(Scimitar::Resources::Base) do
         | 
| 276 | 
            +
                      set_schema ThirdCustomSchema
         | 
| 277 | 
            +
                      extend_schema ExtensionSchema
         | 
| 277 278 |  | 
| 278 | 
            -
             | 
| 279 | 
            -
             | 
| 279 | 
            +
                      def self.endpoint
         | 
| 280 | 
            +
                        '/gaga'
         | 
| 281 | 
            +
                      end
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                      def self.resource_type_id
         | 
| 284 | 
            +
                        'CustomResource'
         | 
| 285 | 
            +
                      end
         | 
| 280 286 | 
             
                    end
         | 
| 287 | 
            +
                  }
         | 
| 281 288 |  | 
| 282 | 
            -
             | 
| 283 | 
            -
             | 
| 289 | 
            +
                  context '#initialize' do
         | 
| 290 | 
            +
                    it 'allows setting extension attributes' do
         | 
| 291 | 
            +
                      resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
         | 
| 292 | 
            +
                      expect(resource.relationship).to eql('GAGA')
         | 
| 284 293 | 
             
                    end
         | 
| 285 | 
            -
                  end
         | 
| 286 | 
            -
             | 
| 294 | 
            +
                  end # "context '#initialize' do"
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                  context '#as_json' do
         | 
| 297 | 
            +
                    it 'namespaces the extension attributes' do
         | 
| 298 | 
            +
                      resource = resource_class.new(relationship: 'GAGA')
         | 
| 299 | 
            +
                      hash = resource.as_json
         | 
| 300 | 
            +
                      expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
         | 
| 301 | 
            +
                      expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
         | 
| 302 | 
            +
                    end
         | 
| 303 | 
            +
                  end # "context '#as_json' do"
         | 
| 287 304 |  | 
| 288 | 
            -
             | 
| 289 | 
            -
             | 
| 290 | 
            -
             | 
| 291 | 
            -
             | 
| 292 | 
            -
             | 
| 293 | 
            -
             | 
| 305 | 
            +
                  context '.resource_type' do
         | 
| 306 | 
            +
                    it 'appends the extension schemas' do
         | 
| 307 | 
            +
                      resource_type = resource_class.resource_type('http://gaga')
         | 
| 308 | 
            +
                      expect(resource_type.meta.location).to eql('http://gaga')
         | 
| 309 | 
            +
                      expect(resource_type.schemaExtensions.count).to eql(1)
         | 
| 310 | 
            +
                    end
         | 
| 294 311 |  | 
| 295 | 
            -
             | 
| 296 | 
            -
             | 
| 297 | 
            -
             | 
| 298 | 
            -
             | 
| 299 | 
            -
                    expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
         | 
| 300 | 
            -
                    expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
         | 
| 301 | 
            -
                  end
         | 
| 302 | 
            -
                end # "context '#as_json' do"
         | 
| 312 | 
            +
                    context 'validation' do
         | 
| 313 | 
            +
                      it 'validates into custom schema' do
         | 
| 314 | 
            +
                        resource = resource_class.new('extension-id' => {})
         | 
| 315 | 
            +
                        expect(resource.valid?).to eql(false)
         | 
| 303 316 |  | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
                     | 
| 309 | 
            -
                  end
         | 
| 317 | 
            +
                        resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
         | 
| 318 | 
            +
                        expect(resource.relationship).to eql('GAGA')
         | 
| 319 | 
            +
                        expect(resource.valid?).to eql(true)
         | 
| 320 | 
            +
                      end
         | 
| 321 | 
            +
                    end # context 'validation'
         | 
| 322 | 
            +
                  end # "context '.resource_type' do"
         | 
| 310 323 |  | 
| 311 | 
            -
                  context ' | 
| 312 | 
            -
                    it ' | 
| 313 | 
            -
                       | 
| 314 | 
            -
                      expect( | 
| 324 | 
            +
                  context '.find_attribute' do
         | 
| 325 | 
            +
                    it 'finds in first schema' do
         | 
| 326 | 
            +
                      found = resource_class().find_attribute('name') # Defined in ThirdCustomSchema
         | 
| 327 | 
            +
                      expect(found).to be_present
         | 
| 328 | 
            +
                      expect(found.name).to eql('name')
         | 
| 329 | 
            +
                      expect(found.type).to eql('string')
         | 
| 330 | 
            +
                    end
         | 
| 315 331 |  | 
| 316 | 
            -
             | 
| 317 | 
            -
                       | 
| 318 | 
            -
                      expect( | 
| 332 | 
            +
                    it 'finds across schemas' do
         | 
| 333 | 
            +
                      found = resource_class().find_attribute('relationship') # Defined in ExtensionSchema
         | 
| 334 | 
            +
                      expect(found).to be_present
         | 
| 335 | 
            +
                      expect(found.name).to eql('relationship')
         | 
| 336 | 
            +
                      expect(found.type).to eql('string')
         | 
| 319 337 | 
             
                    end
         | 
| 320 | 
            -
                  end # context ' | 
| 321 | 
            -
                end # "context ' | 
| 338 | 
            +
                  end # "context '.find_attribute' do"
         | 
| 339 | 
            +
                end # "context 'of custom schema' do"
         | 
| 322 340 |  | 
| 323 | 
            -
                context ' | 
| 324 | 
            -
                   | 
| 325 | 
            -
                     | 
| 326 | 
            -
             | 
| 327 | 
            -
                     | 
| 328 | 
            -
                    expect(found.type).to eql('string')
         | 
| 329 | 
            -
                  end
         | 
| 341 | 
            +
                context 'of core schema' do
         | 
| 342 | 
            +
                  EnterpriseExtensionSchema = Class.new(Scimitar::Schema::Base) do
         | 
| 343 | 
            +
                    def self.id
         | 
| 344 | 
            +
                      'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
         | 
| 345 | 
            +
                    end
         | 
| 330 346 |  | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 333 | 
            -
             | 
| 334 | 
            -
             | 
| 335 | 
            -
             | 
| 347 | 
            +
                    def self.scim_attributes
         | 
| 348 | 
            +
                      [
         | 
| 349 | 
            +
                        Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
         | 
| 350 | 
            +
                        Scimitar::Schema::Attribute.new(name: 'department',   type: 'string')
         | 
| 351 | 
            +
                      ]
         | 
| 352 | 
            +
                    end
         | 
| 336 353 | 
             
                  end
         | 
| 337 | 
            -
             | 
| 354 | 
            +
             | 
| 355 | 
            +
                  let(:resource_class) {
         | 
| 356 | 
            +
                    Class.new(Scimitar::Resources::Base) do
         | 
| 357 | 
            +
                      set_schema Scimitar::Schema::User
         | 
| 358 | 
            +
                      extend_schema EnterpriseExtensionSchema
         | 
| 359 | 
            +
             | 
| 360 | 
            +
                      def self.endpoint
         | 
| 361 | 
            +
                        '/Users'
         | 
| 362 | 
            +
                      end
         | 
| 363 | 
            +
             | 
| 364 | 
            +
                      def self.resource_type_id
         | 
| 365 | 
            +
                        'User'
         | 
| 366 | 
            +
                      end
         | 
| 367 | 
            +
                    end
         | 
| 368 | 
            +
                  }
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                  context '#initialize' do
         | 
| 371 | 
            +
                    it 'allows setting extension attributes' do
         | 
| 372 | 
            +
                      resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {organization: 'SOMEORG', department: 'SOMEDPT'})
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                      expect(resource.organization).to eql('SOMEORG')
         | 
| 375 | 
            +
                      expect(resource.department  ).to eql('SOMEDPT')
         | 
| 376 | 
            +
                    end
         | 
| 377 | 
            +
                  end # "context '#initialize' do"
         | 
| 378 | 
            +
             | 
| 379 | 
            +
                  context '#as_json' do
         | 
| 380 | 
            +
                    it 'namespaces the extension attributes' do
         | 
| 381 | 
            +
                      resource = resource_class.new(organization: 'SOMEORG', department: 'SOMEDPT')
         | 
| 382 | 
            +
                      hash = resource.as_json
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                      expect(hash['schemas']).to eql(['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'])
         | 
| 385 | 
            +
                      expect(hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']).to eql('organization' => 'SOMEORG', 'department' => 'SOMEDPT')
         | 
| 386 | 
            +
                    end
         | 
| 387 | 
            +
                  end # "context '#as_json' do"
         | 
| 388 | 
            +
             | 
| 389 | 
            +
                  context '.resource_type' do
         | 
| 390 | 
            +
                    it 'appends the extension schemas' do
         | 
| 391 | 
            +
                      resource_type = resource_class.resource_type('http://example.com')
         | 
| 392 | 
            +
                      expect(resource_type.meta.location).to eql('http://example.com')
         | 
| 393 | 
            +
                      expect(resource_type.schemaExtensions.count).to eql(1)
         | 
| 394 | 
            +
                    end
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                    context 'validation' do
         | 
| 397 | 
            +
                      it 'validates into custom schema' do
         | 
| 398 | 
            +
                        resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {})
         | 
| 399 | 
            +
                        expect(resource.valid?).to eql(false)
         | 
| 400 | 
            +
             | 
| 401 | 
            +
                        resource = resource_class.new(
         | 
| 402 | 
            +
                          'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
         | 
| 403 | 
            +
                            userName:     'SOMEUSR',
         | 
| 404 | 
            +
                            organization: 'SOMEORG',
         | 
| 405 | 
            +
                            department:   'SOMEDPT'
         | 
| 406 | 
            +
                          }
         | 
| 407 | 
            +
                        )
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                        expect(resource.organization).to eql('SOMEORG')
         | 
| 410 | 
            +
                        expect(resource.department  ).to eql('SOMEDPT')
         | 
| 411 | 
            +
                        expect(resource.valid?      ).to eql(true)
         | 
| 412 | 
            +
                      end
         | 
| 413 | 
            +
                    end # context 'validation'
         | 
| 414 | 
            +
                  end # "context '.resource_type' do"
         | 
| 415 | 
            +
             | 
| 416 | 
            +
                  context '.find_attribute' do
         | 
| 417 | 
            +
                    it 'finds in first schema' do
         | 
| 418 | 
            +
                      found = resource_class().find_attribute('userName') # Defined in Scimitar::Schema::User
         | 
| 419 | 
            +
             | 
| 420 | 
            +
                      expect(found).to be_present
         | 
| 421 | 
            +
                      expect(found.name).to eql('userName')
         | 
| 422 | 
            +
                      expect(found.type).to eql('string')
         | 
| 423 | 
            +
                    end
         | 
| 424 | 
            +
             | 
| 425 | 
            +
                    it 'finds across schemas' do
         | 
| 426 | 
            +
                      found = resource_class().find_attribute('organization') # Defined in EnterpriseExtensionSchema
         | 
| 427 | 
            +
                      expect(found).to be_present
         | 
| 428 | 
            +
                      expect(found.name).to eql('organization')
         | 
| 429 | 
            +
                      expect(found.type).to eql('string')
         | 
| 430 | 
            +
                    end
         | 
| 431 | 
            +
                  end # "context '.find_attribute' do"
         | 
| 432 | 
            +
                end # "context 'of core schema' do"
         | 
| 338 433 | 
             
              end # "context 'schema extension' do"
         | 
| 339 434 | 
             
            end
         | 
| @@ -172,6 +172,7 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 172 172 | 
             
                      instance.work_email_address = 'foo.bar@test.com'
         | 
| 173 173 | 
             
                      instance.home_email_address = nil
         | 
| 174 174 | 
             
                      instance.work_phone_number  = '+642201234567'
         | 
| 175 | 
            +
                      instance.organization       = 'SOMEORG'
         | 
| 175 176 |  | 
| 176 177 | 
             
                      g1 = MockGroup.create!(display_name: 'Group 1')
         | 
| 177 178 | 
             
                      g2 = MockGroup.create!(display_name: 'Group 2')
         | 
| @@ -194,7 +195,12 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 194 195 | 
             
                        'externalId'  => 'AA02984',
         | 
| 195 196 | 
             
                        'groups'      => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
         | 
| 196 197 | 
             
                        'meta'        => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
         | 
| 197 | 
            -
                        'schemas'     => ['urn:ietf:params:scim:schemas:core:2.0:User']
         | 
| 198 | 
            +
                        'schemas'     => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                        'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
         | 
| 201 | 
            +
                          'organization' => 'SOMEORG',
         | 
| 202 | 
            +
                          'department'   => nil
         | 
| 203 | 
            +
                        }
         | 
| 198 204 | 
             
                      })
         | 
| 199 205 | 
             
                    end
         | 
| 200 206 | 
             
                  end # "context 'with a UUID, renamed primary key column' do"
         | 
| @@ -318,7 +324,9 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 318 324 | 
             
                          ],
         | 
| 319 325 |  | 
| 320 326 | 
             
                          'meta'    => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
         | 
| 321 | 
            -
                          'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
         | 
| 327 | 
            +
                          'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
         | 
| 328 | 
            +
             | 
| 329 | 
            +
                          'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
         | 
| 322 330 | 
             
                        })
         | 
| 323 331 | 
             
                      end
         | 
| 324 332 | 
             
                    end # "context 'using static mappings' do"
         | 
| @@ -345,7 +353,9 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 345 353 | 
             
                          ],
         | 
| 346 354 |  | 
| 347 355 | 
             
                          'meta'    => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
         | 
| 348 | 
            -
                          'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
         | 
| 356 | 
            +
                          'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
         | 
| 357 | 
            +
             | 
| 358 | 
            +
                          'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
         | 
| 349 359 | 
             
                        })
         | 
| 350 360 | 
             
                      end
         | 
| 351 361 | 
             
                    end # "context 'using dynamic lists' do"
         | 
| @@ -402,7 +412,12 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 402 412 | 
             
                          'id'           => '42', # Note, String
         | 
| 403 413 | 
             
                          'externalId'   => 'AA02984',
         | 
| 404 414 | 
             
                          'meta'         => {'location' => 'https://test.com/mock_users/42', 'resourceType' => 'User'},
         | 
| 405 | 
            -
                          'schemas'      => ['urn:ietf:params:scim:schemas:core:2.0:User']
         | 
| 415 | 
            +
                          'schemas'      => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
         | 
| 416 | 
            +
             | 
| 417 | 
            +
                          'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
         | 
| 418 | 
            +
                            'organization' => 'SOMEORG',
         | 
| 419 | 
            +
                            'DEPARTMENT'   => 'SOMEDPT'
         | 
| 420 | 
            +
                          }
         | 
| 406 421 | 
             
                        }
         | 
| 407 422 |  | 
| 408 423 | 
             
                        hash = spec_helper_hupcase(hash) if force_upper_case
         | 
| @@ -418,6 +433,8 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 418 433 | 
             
                        expect(instance.work_email_address).to eql('foo.bar@test.com')
         | 
| 419 434 | 
             
                        expect(instance.home_email_address).to be_nil
         | 
| 420 435 | 
             
                        expect(instance.work_phone_number ).to eql('+642201234567')
         | 
| 436 | 
            +
                        expect(instance.organization      ).to eql('SOMEORG')
         | 
| 437 | 
            +
                        expect(instance.department        ).to eql('SOMEDPT')
         | 
| 421 438 | 
             
                      end
         | 
| 422 439 |  | 
| 423 440 | 
             
                      it 'honouring read-write lists' do
         | 
| @@ -704,6 +721,21 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 704 721 | 
             
                            expect(scim_hash['name']['familyName']).to eql('Bar')
         | 
| 705 722 | 
             
                          end
         | 
| 706 723 |  | 
| 724 | 
            +
                          it 'with schema extensions: overwrites' do
         | 
| 725 | 
            +
                            path      = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
         | 
| 726 | 
            +
                            scim_hash = { 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => { 'organization' => 'SOMEORG' } }.with_indifferent_case_insensitive_access()
         | 
| 727 | 
            +
             | 
| 728 | 
            +
                            @instance.send(
         | 
| 729 | 
            +
                              :from_patch_backend!,
         | 
| 730 | 
            +
                              nature:        'add',
         | 
| 731 | 
            +
                              path:          path,
         | 
| 732 | 
            +
                              value:         'OTHERORG',
         | 
| 733 | 
            +
                              altering_hash: scim_hash
         | 
| 734 | 
            +
                            )
         | 
| 735 | 
            +
             | 
| 736 | 
            +
                            expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
         | 
| 737 | 
            +
                          end
         | 
| 738 | 
            +
             | 
| 707 739 | 
             
                          # For 'add', filter at end-of-path is nonsensical and not
         | 
| 708 740 | 
             
                          # supported by spec or Scimitar; we only test mid-path filters.
         | 
| 709 741 | 
             
                          #
         | 
| @@ -892,6 +924,21 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 892 924 | 
             
                            expect(scim_hash['name']['givenName']).to eql('Baz')
         | 
| 893 925 | 
             
                          end
         | 
| 894 926 |  | 
| 927 | 
            +
                          it 'with schema extensions: adds' do
         | 
| 928 | 
            +
                            path      = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
         | 
| 929 | 
            +
                            scim_hash = {}.with_indifferent_case_insensitive_access()
         | 
| 930 | 
            +
             | 
| 931 | 
            +
                            @instance.send(
         | 
| 932 | 
            +
                              :from_patch_backend!,
         | 
| 933 | 
            +
                              nature:        'add',
         | 
| 934 | 
            +
                              path:          path,
         | 
| 935 | 
            +
                              value:         'SOMEORG',
         | 
| 936 | 
            +
                              altering_hash: scim_hash
         | 
| 937 | 
            +
                            )
         | 
| 938 | 
            +
             | 
| 939 | 
            +
                            expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
         | 
| 940 | 
            +
                          end
         | 
| 941 | 
            +
             | 
| 895 942 | 
             
                          context 'with filter mid-path: adds' do
         | 
| 896 943 | 
             
                            it 'by string match' do
         | 
| 897 944 | 
             
                              path      = [ 'emails[type eq "work"]', 'value' ]
         | 
| @@ -1233,7 +1280,7 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 1233 1280 |  | 
| 1234 1281 | 
             
                          # What we expect:
         | 
| 1235 1282 | 
             
                          #
         | 
| 1236 | 
            -
                          # https:// | 
| 1283 | 
            +
                          # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
         | 
| 1237 1284 | 
             
                          # https://docs.snowflake.com/en/user-guide/scim-intro.html#patch-scim-v2-groups-id
         | 
| 1238 1285 | 
             
                          #
         | 
| 1239 1286 | 
             
                          # ...vs accounting for the unusual payloads we sometimes get,
         | 
| @@ -2680,6 +2727,38 @@ RSpec.describe Scimitar::Resources::Mixin do | |
| 2680 2727 | 
             
                        expect(@instance.first_name).to eql('Baz')
         | 
| 2681 2728 | 
             
                      end
         | 
| 2682 2729 |  | 
| 2730 | 
            +
                      # Note odd ":" separating schema ID from first attribute, although
         | 
| 2731 | 
            +
                      # the nature of JSON rendering / other payloads might lead you to
         | 
| 2732 | 
            +
                      # expect a "." as with any other path component.
         | 
| 2733 | 
            +
                      #
         | 
| 2734 | 
            +
                      # Note the ":" separating the schema ID (URN) from the attribute.
         | 
| 2735 | 
            +
                      # The nature of JSON rendering / other payloads might lead you to
         | 
| 2736 | 
            +
                      # expect a "." as with any complex types, but that's not the case;
         | 
| 2737 | 
            +
                      # see https://tools.ietf.org/html/rfc7644#section-3.10, or
         | 
| 2738 | 
            +
                      # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
         | 
| 2739 | 
            +
                      # particular, https://tools.ietf.org/html/rfc7644#page-35.
         | 
| 2740 | 
            +
                      #
         | 
| 2741 | 
            +
                      it 'which updates attributes defined by extension schema' do
         | 
| 2742 | 
            +
                        @instance.update!(department: 'SOMEDPT')
         | 
| 2743 | 
            +
             | 
| 2744 | 
            +
                        path = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department'
         | 
| 2745 | 
            +
                        path = path.upcase if force_upper_case
         | 
| 2746 | 
            +
             | 
| 2747 | 
            +
                        patch = {
         | 
| 2748 | 
            +
                          'schemas'    => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
         | 
| 2749 | 
            +
                          'Operations' => [
         | 
| 2750 | 
            +
                            {
         | 
| 2751 | 
            +
                              'op'    => 'replace',
         | 
| 2752 | 
            +
                              'path'  => path,
         | 
| 2753 | 
            +
                              'value' => 'OTHERDPT'
         | 
| 2754 | 
            +
                            }
         | 
| 2755 | 
            +
                          ]
         | 
| 2756 | 
            +
                        }
         | 
| 2757 | 
            +
             | 
| 2758 | 
            +
                        @instance.from_scim_patch!(patch_hash: patch)
         | 
| 2759 | 
            +
                        expect(@instance.department).to eql('OTHERDPT')
         | 
| 2760 | 
            +
                      end
         | 
| 2761 | 
            +
             | 
| 2683 2762 | 
             
                      it 'which updates with filter match' do
         | 
| 2684 2763 | 
             
                        @instance.update!(work_email_address: 'work@test.com', home_email_address: 'home@test.com')
         | 
| 2685 2764 |  | 
| @@ -760,7 +760,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do | |
| 760 760 | 
             
                    end
         | 
| 761 761 | 
             
                  end
         | 
| 762 762 |  | 
| 763 | 
            -
                  # https:// | 
| 763 | 
            +
                  # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
         | 
| 764 764 | 
             
                  #
         | 
| 765 765 | 
             
                  context 'and using an RFC-compliant payload' do
         | 
| 766 766 | 
             
                    let(:removed_user) { @u2 }
         | 
    
        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.4. | 
| 4 | 
            +
              version: 2.4.1
         | 
| 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: 2023- | 
| 12 | 
            +
            date: 2023-03-02 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rails
         | 
| @@ -45,14 +45,14 @@ dependencies: | |
| 45 45 | 
             
                requirements:
         | 
| 46 46 | 
             
                - - "~>"
         | 
| 47 47 | 
             
                  - !ruby/object:Gem::Version
         | 
| 48 | 
            -
                    version: '1. | 
| 48 | 
            +
                    version: '1.4'
         | 
| 49 49 | 
             
              type: :development
         | 
| 50 50 | 
             
              prerelease: false
         | 
| 51 51 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 52 52 | 
             
                requirements:
         | 
| 53 53 | 
             
                - - "~>"
         | 
| 54 54 | 
             
                  - !ruby/object:Gem::Version
         | 
| 55 | 
            -
                    version: '1. | 
| 55 | 
            +
                    version: '1.4'
         | 
| 56 56 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 57 57 | 
             
              name: simplecov-rcov
         | 
| 58 58 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -73,28 +73,28 @@ dependencies: | |
| 73 73 | 
             
                requirements:
         | 
| 74 74 | 
             
                - - "~>"
         | 
| 75 75 | 
             
                  - !ruby/object:Gem::Version
         | 
| 76 | 
            -
                    version: '6. | 
| 76 | 
            +
                    version: '6.5'
         | 
| 77 77 | 
             
              type: :development
         | 
| 78 78 | 
             
              prerelease: false
         | 
| 79 79 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 80 80 | 
             
                requirements:
         | 
| 81 81 | 
             
                - - "~>"
         | 
| 82 82 | 
             
                  - !ruby/object:Gem::Version
         | 
| 83 | 
            -
                    version: '6. | 
| 83 | 
            +
                    version: '6.5'
         | 
| 84 84 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 85 85 | 
             
              name: rspec-rails
         | 
| 86 86 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 87 87 | 
             
                requirements:
         | 
| 88 88 | 
             
                - - "~>"
         | 
| 89 89 | 
             
                  - !ruby/object:Gem::Version
         | 
| 90 | 
            -
                    version: ' | 
| 90 | 
            +
                    version: '6.0'
         | 
| 91 91 | 
             
              type: :development
         | 
| 92 92 | 
             
              prerelease: false
         | 
| 93 93 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 94 94 | 
             
                requirements:
         | 
| 95 95 | 
             
                - - "~>"
         | 
| 96 96 | 
             
                  - !ruby/object:Gem::Version
         | 
| 97 | 
            -
                    version: ' | 
| 97 | 
            +
                    version: '6.0'
         | 
| 98 98 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 99 99 | 
             
              name: byebug
         | 
| 100 100 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -115,14 +115,14 @@ dependencies: | |
| 115 115 | 
             
                requirements:
         | 
| 116 116 | 
             
                - - "~>"
         | 
| 117 117 | 
             
                  - !ruby/object:Gem::Version
         | 
| 118 | 
            -
                    version: '1. | 
| 118 | 
            +
                    version: '1.3'
         | 
| 119 119 | 
             
              type: :development
         | 
| 120 120 | 
             
              prerelease: false
         | 
| 121 121 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 122 122 | 
             
                requirements:
         | 
| 123 123 | 
             
                - - "~>"
         | 
| 124 124 | 
             
                  - !ruby/object:Gem::Version
         | 
| 125 | 
            -
                    version: '1. | 
| 125 | 
            +
                    version: '1.3'
         | 
| 126 126 | 
             
            description: SCIM v2 support for Users and Groups in Ruby On Rails
         | 
| 127 127 | 
             
            email:
         | 
| 128 128 | 
             
            - dev@ripaglobal.com
         |