Comparing changes

v0.1.0 v0.2.0
14 commits 17 files changed

Commits

95724d7 use meta mo 2019-01-05 00:32:42
c31ef28 extract meta class mo 2019-01-05 00:30:01
6d381b7 implement respond_to_missing? mo 2019-01-04 23:58:50
cf1898d add filter support mo 2019-01-04 23:51:44
0a97f32 add bulk support mo 2019-01-04 23:48:59
2d40104 add other supportable features mo 2019-01-04 23:28:36
1ce0672 with etag support mo 2019-01-04 23:24:51
ac6b929 add authentication schemes mo 2019-01-04 22:58:04
c952d05 fix linter error mo 2019-01-04 18:31:27
lib/scim/kit/v2/templates/authentication_scheme.json.jbuilder
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+json.key_format! camelize: :lower
+json.name name
+json.description description
+json.spec_uri spec_uri
+json.documentation_uri documentation_uri
+json.type type
+json.primary primary if primary
lib/scim/kit/v2/templates/meta.json.jbuilder
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+json.key_format! camelize: :lower
+json.location location
+json.resource_type resource_type
+json.created created.iso8601 if created
+json.last_modified last_modified.iso8601 if last_modified
+json.version version.to_i if version
lib/scim/kit/v2/templates/nil_class.json.jbuilder
lib/scim/kit/v2/templates/resource_type.json.jbuilder
@@ -2,8 +2,7 @@
 
 json.key_format! camelize: :lower
 json.meta do
-  json.resource_type 'ResourceType'
-  json.location location
+  render meta, json: json
 end
 json.schemas [Scim::Kit::V2::Schema::RESOURCE_TYPE]
 json.id id
lib/scim/kit/v2/templates/service_provider_configuration.json.jbuilder
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+json.key_format! camelize: :lower
+json.schemas [Scim::Kit::V2::Schema::SERVICE_PROVIDER_CONFIGURATION]
+json.documentation_uri documentation_uri
+json.patch do
+  render patch, json: json
+end
+json.bulk do
+  render bulk, json: json
+end
+json.filter do
+  render filter, json: json
+end
+json.change_password do
+  render change_password, json: json
+end
+json.sort do
+  render sort, json: json
+end
+json.etag do
+  render etag, json: json
+end
+json.authentication_schemes authentication_schemes do |authentication_scheme|
+  render authentication_scheme, json: json
+end
+json.meta do
+  render meta, json: json
+end
lib/scim/kit/v2/templates/supportable.json.jbuilder
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+json.key_format! camelize: :lower
+json.supported supported
+@dynamic_attributes.each do |key, value|
+  json.set! key.to_s.delete('='), value
+end
lib/scim/kit/v2/authentication_scheme.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Scim
+  module Kit
+    module V2
+      # Represents the available Authentication Schemes.
+      class AuthenticationScheme
+        DEFAULTS = {
+          httpbasic: {
+            description: 'Authentication scheme using the HTTP Basic Standard',
+            documentation_uri: 'http://example.com/help/httpBasic.html',
+            name: 'HTTP Basic',
+            spec_uri: 'http://www.rfc-editor.org/info/rfc2617'
+          },
+          oauthbearertoken: {
+            description:
+            'Authentication scheme using the OAuth Bearer Token Standard',
+            documentation_uri: 'http://example.com/help/oauth.html',
+            name: 'OAuth Bearer Token',
+            spec_uri: 'http://www.rfc-editor.org/info/rfc6750'
+          }
+        }.freeze
+        include Templatable
+        attr_accessor :name
+        attr_accessor :description
+        attr_accessor :documentation_uri
+        attr_accessor :spec_uri
+        attr_accessor :type
+        attr_accessor :primary
+
+        def initialize
+          yield self if block_given?
+        end
+
+        def self.build_for(type, primary: nil)
+          defaults = DEFAULTS[type.to_sym] || {}
+          new do |x|
+            x.type = type
+            x.primary = primary
+            x.description = defaults[:description]
+            x.documentation_uri = defaults[:documentation_uri]
+            x.name = defaults[:name]
+            x.spec_uri = defaults[:spec_uri]
+          end
+        end
+      end
+    end
+  end
+end
lib/scim/kit/v2/meta.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Scim
+  module Kit
+    module V2
+      # Represents a meta section
+      class Meta
+        include Templatable
+
+        attr_accessor :created, :last_modified, :version
+        attr_reader :location
+        attr_reader :resource_type
+
+        def initialize(resource_type, location)
+          @resource_type = resource_type
+          @location = location
+          @version = @created = @last_modified = Time.now
+        end
+      end
+    end
+  end
+end
lib/scim/kit/v2/resource_type.rb
@@ -12,10 +12,11 @@ module Scim
         attr_accessor :description
         attr_accessor :endpoint
         attr_accessor :schema
-        attr_reader :location
+        attr_reader :meta
 
         def initialize(location:)
-          @location = location
+          @meta = Meta.new('ResourceType', location)
+          @meta.version = @meta.created = @meta.last_modified = nil
         end
 
         def self.build(*args)
lib/scim/kit/v2/schema.rb
@@ -6,10 +6,13 @@ module Scim
       # Represents a SCIM Schema
       class Schema
         include Templatable
+
+        CORE = 'urn:ietf:params:scim:schemas:core:2.0'
         ERROR = 'urn:ietf:params:scim:api:messages:2.0:Error'
-        GROUP = 'urn:ietf:params:scim:schemas:core:2.0:Group'
-        RESOURCE_TYPE = 'urn:ietf:params:scim:schemas:core:2.0:ResourceType'
-        USER = 'urn:ietf:params:scim:schemas:core:2.0:User'
+        GROUP = "#{CORE}:Group"
+        RESOURCE_TYPE = "#{CORE}:ResourceType"
+        SERVICE_PROVIDER_CONFIGURATION = "#{CORE}:ServiceProviderConfig"
+        USER = "#{CORE}:User"
 
         attr_reader :id, :name, :location, :attributes
         attr_accessor :description
lib/scim/kit/v2/service_provider_configuration.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Scim
+  module Kit
+    module V2
+      # Represents a scim Service Provider Configuration
+      class ServiceProviderConfiguration
+        include Templatable
+        attr_reader :location
+        attr_accessor :documentation_uri
+        attr_reader :authentication_schemes
+        attr_reader :etag, :sort, :change_password, :patch
+        attr_reader :bulk, :filter, :meta
+
+        def initialize(location:)
+          @meta = Meta.new('ServiceProviderConfig', location)
+          @authentication_schemes = []
+          @etag = Supportable.new
+          @sort = Supportable.new
+          @change_password = Supportable.new
+          @patch = Supportable.new
+          @bulk = Supportable.new(:max_operations, :max_payload_size)
+          @filter = Supportable.new(:max_results)
+        end
+
+        def add_authentication(type, primary: nil)
+          scheme = AuthenticationScheme.build_for(type, primary: primary)
+          yield scheme if block_given?
+          @authentication_schemes << scheme
+        end
+      end
+    end
+  end
+end
lib/scim/kit/v2/supportable.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Scim
+  module Kit
+    module V2
+      # Represents a Feature
+      class Supportable
+        include Templatable
+        include DynamicAttributes
+
+        attr_accessor :supported
+
+        def initialize(*dynamic_attributes)
+          @dynamic_attributes = Hash[
+            dynamic_attributes.map { |x| ["#{x}=".to_sym, nil] }
+          ]
+          @supported = false
+        end
+      end
+    end
+  end
+end
lib/scim/kit/dynamic_attributes.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Scim
+  module Kit
+    # Allows dynamic assignment of attributes.
+    module DynamicAttributes
+      def method_missing(method, *args)
+        return super unless respond_to_missing?(method)
+
+        @dynamic_attributes[method] = args[0]
+      end
+
+      def respond_to_missing?(method, _include_private = false)
+        @dynamic_attributes.key?(method) || super
+      end
+    end
+  end
+end
lib/scim/kit/templatable.rb
@@ -8,7 +8,7 @@ module Scim
         render(self, options)
       end
 
-      def as_json(options = nil)
+      def as_json(_options = nil)
         to_h
       end
 
lib/scim/kit/version.rb
@@ -2,6 +2,6 @@
 
 module Scim
   module Kit
-    VERSION = '0.1.0'
+    VERSION = '0.2.0'
   end
 end
lib/scim/kit.rb
@@ -3,15 +3,20 @@
 require 'tilt'
 require 'tilt/jbuilder'
 
+require 'scim/kit/dynamic_attributes'
 require 'scim/kit/templatable'
 require 'scim/kit/template'
 require 'scim/kit/version'
 
 require 'scim/kit/v2/attribute_type'
+require 'scim/kit/v2/authentication_scheme'
+require 'scim/kit/v2/meta'
 require 'scim/kit/v2/mutability'
 require 'scim/kit/v2/resource_type'
 require 'scim/kit/v2/returned'
 require 'scim/kit/v2/schema'
+require 'scim/kit/v2/service_provider_configuration'
+require 'scim/kit/v2/supportable'
 require 'scim/kit/v2/uniqueness'
 
 module Scim
spec/scim/kit/v2/service_provider_configuration_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+RSpec.describe Scim::Kit::V2::ServiceProviderConfiguration do
+  subject { described_class.new(location: location) }
+
+  let(:location) { FFaker::Internet.uri('https') }
+  let(:now) { Time.now }
+
+  describe '#to_json' do
+    let(:result) { JSON.parse(subject.to_json, symbolize_names: true) }
+
+    specify { expect(result[:schemas]).to match_array(['urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig']) }
+    specify { expect(result[:documentationUri]).to be_blank }
+    specify { expect(result[:patch][:supported]).to be(false) }
+    specify { expect(result[:bulk][:supported]).to be(false) }
+    specify { expect(result[:filter][:supported]).to be(false) }
+    specify { expect(result[:changePassword][:supported]).to be(false) }
+    specify { expect(result[:sort][:supported]).to be(false) }
+    specify { expect(result[:etag][:supported]).to be(false) }
+    specify { expect(result[:authenticationSchemes]).to be_empty }
+    specify { expect(result[:meta][:location]).to eql(location) }
+    specify { expect(result[:meta][:resourceType]).to eql('ServiceProviderConfig') }
+    specify { expect(result[:meta][:created]).to eql(now.iso8601) }
+    specify { expect(result[:meta][:lastModified]).to eql(now.iso8601) }
+    specify { expect(result[:meta][:version]).not_to be_nil }
+
+    context 'with documentation uri' do
+      before do
+        subject.documentation_uri = FFaker::Internet.uri('https')
+      end
+
+      specify { expect(result[:documentationUri]).to eql(subject.documentation_uri) }
+    end
+
+    context 'with OAuth Bearer Token' do
+      before { subject.add_authentication(:oauthbearertoken) }
+
+      specify { expect(result[:authenticationSchemes][0][:name]).to eql('OAuth Bearer Token') }
+      specify { expect(result[:authenticationSchemes][0][:description]).to eql('Authentication scheme using the OAuth Bearer Token Standard') }
+      specify { expect(result[:authenticationSchemes][0][:specUri]).to eql('http://www.rfc-editor.org/info/rfc6750') }
+      specify { expect(result[:authenticationSchemes][0][:documentationUri]).to eql('http://example.com/help/oauth.html') }
+      specify { expect(result[:authenticationSchemes][0][:type]).to eql('oauthbearertoken') }
+    end
+
+    context 'with http basic' do
+      before { subject.add_authentication(:httpbasic) }
+
+      specify { expect(result[:authenticationSchemes][0][:name]).to eql('HTTP Basic') }
+      specify { expect(result[:authenticationSchemes][0][:description]).to eql('Authentication scheme using the HTTP Basic Standard') }
+      specify { expect(result[:authenticationSchemes][0][:specUri]).to eql('http://www.rfc-editor.org/info/rfc2617') }
+      specify { expect(result[:authenticationSchemes][0][:documentationUri]).to eql('http://example.com/help/httpBasic.html') }
+      specify { expect(result[:authenticationSchemes][0][:type]).to eql('httpbasic') }
+    end
+
+    context 'with multiple schemes' do
+      before do
+        subject.add_authentication(:oauthbearertoken, primary: true)
+        subject.add_authentication(:httpbasic)
+      end
+
+      specify { expect(result[:authenticationSchemes][0][:name]).to eql('OAuth Bearer Token') }
+      specify { expect(result[:authenticationSchemes][0][:description]).to eql('Authentication scheme using the OAuth Bearer Token Standard') }
+      specify { expect(result[:authenticationSchemes][0][:specUri]).to eql('http://www.rfc-editor.org/info/rfc6750') }
+      specify { expect(result[:authenticationSchemes][0][:documentationUri]).to eql('http://example.com/help/oauth.html') }
+      specify { expect(result[:authenticationSchemes][0][:type]).to eql('oauthbearertoken') }
+      specify { expect(result[:authenticationSchemes][0][:primary]).to be(true) }
+
+      specify { expect(result[:authenticationSchemes][1][:name]).to eql('HTTP Basic') }
+      specify { expect(result[:authenticationSchemes][1][:description]).to eql('Authentication scheme using the HTTP Basic Standard') }
+      specify { expect(result[:authenticationSchemes][1][:specUri]).to eql('http://www.rfc-editor.org/info/rfc2617') }
+      specify { expect(result[:authenticationSchemes][1][:documentationUri]).to eql('http://example.com/help/httpBasic.html') }
+      specify { expect(result[:authenticationSchemes][1][:type]).to eql('httpbasic') }
+    end
+
+    context 'with custom scheme' do
+      before do
+        subject.add_authentication(:custom) do |x|
+          x.name = 'custom'
+          x.description = 'custom'
+          x.spec_uri = 'http://www.rfc-editor.org/info/rfcXXXX'
+          x.documentation_uri = 'http://example.com/help/custom.html'
+        end
+      end
+
+      specify { expect(result[:authenticationSchemes][0][:name]).to eql('custom') }
+      specify { expect(result[:authenticationSchemes][0][:description]).to eql('custom') }
+      specify { expect(result[:authenticationSchemes][0][:specUri]).to eql('http://www.rfc-editor.org/info/rfcXXXX') }
+      specify { expect(result[:authenticationSchemes][0][:documentationUri]).to eql('http://example.com/help/custom.html') }
+      specify { expect(result[:authenticationSchemes][0][:type]).to eql('custom') }
+    end
+
+    context 'with etag support' do
+      before { subject.etag.supported = true }
+
+      specify { expect(result[:etag][:supported]).to be(true) }
+    end
+
+    context 'with sort support' do
+      before { subject.sort.supported = true }
+
+      specify { expect(result[:sort][:supported]).to be(true) }
+    end
+
+    context 'with change_password support' do
+      before { subject.change_password.supported = true }
+
+      specify { expect(result[:changePassword][:supported]).to be(true) }
+    end
+
+    context 'with patch support' do
+      before { subject.patch.supported = true }
+
+      specify { expect(result[:patch][:supported]).to be(true) }
+    end
+
+    context 'with bulk support' do
+      before do
+        subject.bulk.supported = true
+        subject.bulk.max_operations = 1000
+        subject.bulk.max_payload_size = 1_048_576
+      end
+
+      specify { expect(result[:bulk][:supported]).to be(true) }
+      specify { expect(result[:bulk][:maxOperations]).to be(1000) }
+      specify { expect(result[:bulk][:maxPayloadSize]).to be(1_048_576) }
+    end
+
+    context 'with filter support' do
+      before do
+        subject.filter.supported = true
+        subject.filter.max_results = 200
+      end
+
+      specify { expect(result[:filter][:supported]).to be(true) }
+      specify { expect(result[:filter][:maxResults]).to be(200) }
+    end
+  end
+end