Commit b47ab1d
Changed files (6)
lib
scim
kit
spec
scim
lib/scim/kit/cli/canonical_keys.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Scim
+ module Kit
+ module Cli
+ module CanonicalKeys
+ class << self
+ def apply(schema, data)
+ return data unless schema.is_a?(Hash)
+
+ case data
+ when Hash then object(schema, data)
+ when Array then array(schema, data)
+ else data
+ end
+ end
+
+ private
+
+ def object(schema, data)
+ properties = schema['properties']
+ return data unless properties.is_a?(Hash)
+
+ declared = index(properties)
+ data.to_h do |key, value|
+ name = declared.fetch(key.to_s.downcase, key)
+ [name, apply(properties[name], value)]
+ end
+ end
+
+ def array(schema, data)
+ items = schema['items']
+ return data unless items.is_a?(Hash)
+
+ data.map { |value| apply(items, value) }
+ end
+
+ def index(properties)
+ properties.keys.to_h { |name| [name.downcase, name] }
+ end
+ end
+ end
+ end
+ end
+end
lib/scim/kit/cli/validator.rb
@@ -7,7 +7,7 @@ module Scim
class << self
def errors_for(schema, data)
JSONSchemer.schema(schema)
- .validate(normalize(data))
+ .validate(CanonicalKeys.apply(schema, normalize(data)))
.map { |error| JSONSchemer::Errors.pretty(error) }
end
lib/scim/kit/cli.rb
@@ -4,6 +4,7 @@ require 'json_schemer'
require 'thor'
require 'scim/kit/cli/app'
+require 'scim/kit/cli/canonical_keys'
require 'scim/kit/cli/client'
require 'scim/kit/cli/discovery'
require 'scim/kit/cli/reporter'
spec/scim/kit/cli/canonical_keys_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+RSpec.describe Scim::Kit::Cli::CanonicalKeys do
+ let(:schema) do
+ {
+ 'type' => 'object',
+ 'properties' => {
+ 'userName' => { 'type' => 'string' },
+ 'name' => {
+ 'type' => 'object',
+ 'properties' => { 'givenName' => { 'type' => 'string' } }
+ },
+ 'emails' => {
+ 'type' => 'array',
+ 'items' => {
+ 'type' => 'object',
+ 'properties' => { 'value' => { 'type' => 'string' } }
+ }
+ }
+ }
+ }
+ end
+
+ describe '.apply' do
+ it 'renames an upcased key to the declared spelling' do
+ expect(described_class.apply(schema, 'USERNAME' => 'mo'))
+ .to eql('userName' => 'mo')
+ end
+
+ it 'renames a downcased key to the declared spelling' do
+ expect(described_class.apply(schema, 'username' => 'mo'))
+ .to eql('userName' => 'mo')
+ end
+
+ it 'leaves a canonical key alone' do
+ expect(described_class.apply(schema, 'userName' => 'mo'))
+ .to eql('userName' => 'mo')
+ end
+
+ it 'renames nested sub-attributes' do
+ result = described_class.apply(schema, 'NAME' => { 'GIVENNAME' => 'mo' })
+
+ expect(result).to eql('name' => { 'givenName' => 'mo' })
+ end
+
+ it 'renames keys inside array items' do
+ result = described_class.apply(schema, 'EMAILS' => [{ 'VALUE' => 'a@b' }])
+
+ expect(result).to eql('emails' => [{ 'value' => 'a@b' }])
+ end
+
+ it 'passes undeclared vendor keys through untouched' do
+ result = described_class.apply(schema, 'urn:vendor:X' => { 'A' => 1 })
+
+ expect(result).to eql('urn:vendor:X' => { 'A' => 1 })
+ end
+
+ it 'leaves values alone' do
+ expect(described_class.apply(schema, 'USERNAME' => 'MiXeD'))
+ .to eql('userName' => 'MiXeD')
+ end
+
+ it 'returns non-object data unchanged' do
+ expect(described_class.apply(schema, 'nope')).to eql('nope')
+ end
+
+ it 'returns data unchanged when the schema declares no properties' do
+ expect(described_class.apply({ 'type' => 'object' }, 'A' => 1))
+ .to eql('A' => 1)
+ end
+ end
+end
spec/scim/kit/cli/validator_spec.rb
@@ -29,6 +29,18 @@ RSpec.describe Scim::Kit::Cli::Validator do
expect(errors).to eql(["property '/userName' is not of type: string"])
end
+ it 'accepts a declared attribute the server spelled differently' do
+ errors = described_class.errors_for(schema, { USERNAME: 'bjensen' })
+
+ expect(errors).to eql([])
+ end
+
+ it 'reports the canonical name when a differently spelled value is wrong' do
+ errors = described_class.errors_for(schema, { USERNAME: 1 })
+
+ expect(errors).to eql(["property '/userName' is not of type: string"])
+ end
+
it 'returns a readable error for an undeclared property' do
errors = described_class.errors_for(schema, { userName: 'bjensen', extra: true })
README.md
@@ -126,6 +126,10 @@ types, canonical values, and required attributes the server declares. Every
and `--attributes` relaxes the required checks so sparse responses are not
reported as errors.
+Attribute names are matched case insensitively per RFC 7643 §2.1, so a server
+that returns `USERNAME` where its schema declares `userName` is not reported as
+non-conforming. Errors always name the attribute as the schema declares it.
+
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.