scim_engine 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +25 -0
- data/app/controllers/scim_engine/application_controller.rb +36 -0
- data/app/controllers/scim_engine/resource_types_controller.rb +22 -0
- data/app/controllers/scim_engine/resources_controller.rb +71 -0
- data/app/controllers/scim_engine/schemas_controller.rb +16 -0
- data/app/controllers/scim_engine/service_provider_configurations_controller.rb +8 -0
- data/app/models/scim_engine/authentication_error.rb +9 -0
- data/app/models/scim_engine/authentication_scheme.rb +12 -0
- data/app/models/scim_engine/bulk.rb +5 -0
- data/app/models/scim_engine/complex_types/base.rb +19 -0
- data/app/models/scim_engine/complex_types/email.rb +11 -0
- data/app/models/scim_engine/complex_types/name.rb +7 -0
- data/app/models/scim_engine/complex_types/reference.rb +7 -0
- data/app/models/scim_engine/error_response.rb +13 -0
- data/app/models/scim_engine/errors.rb +14 -0
- data/app/models/scim_engine/filter.rb +5 -0
- data/app/models/scim_engine/meta.rb +7 -0
- data/app/models/scim_engine/not_found_error.rb +10 -0
- data/app/models/scim_engine/resource_invalid_error.rb +9 -0
- data/app/models/scim_engine/resource_type.rb +28 -0
- data/app/models/scim_engine/resources/base.rb +110 -0
- data/app/models/scim_engine/resources/group.rb +13 -0
- data/app/models/scim_engine/resources/user.rb +13 -0
- data/app/models/scim_engine/schema/attribute.rb +82 -0
- data/app/models/scim_engine/schema/base.rb +30 -0
- data/app/models/scim_engine/schema/derived_attributes.rb +24 -0
- data/app/models/scim_engine/schema/email.rb +13 -0
- data/app/models/scim_engine/schema/group.rb +25 -0
- data/app/models/scim_engine/schema/name.rb +15 -0
- data/app/models/scim_engine/schema/reference.rb +12 -0
- data/app/models/scim_engine/schema/user.rb +28 -0
- data/app/models/scim_engine/service_provider_configuration.rb +30 -0
- data/app/models/scim_engine/supportable.rb +10 -0
- data/app/views/layouts/scim_engine/application.html.erb +14 -0
- data/config/routes.rb +6 -0
- data/lib/scim_engine/engine.rb +35 -0
- data/lib/scim_engine/version.rb +3 -0
- data/lib/scim_engine.rb +13 -0
- data/lib/tasks/scim_engine_tasks.rake +4 -0
- data/spec/controllers/scim_engine/application_controller_spec.rb +104 -0
- data/spec/controllers/scim_engine/resource_types_controller_spec.rb +78 -0
- data/spec/controllers/scim_engine/resources_controller_spec.rb +134 -0
- data/spec/controllers/scim_engine/schemas_controller_spec.rb +66 -0
- data/spec/controllers/scim_engine/service_provider_configurations_controller_spec.rb +21 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +16 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/log/test.log +863 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/models/scim_engine/complex_types/email_spec.rb +23 -0
- data/spec/models/scim_engine/resource_type_spec.rb +21 -0
- data/spec/models/scim_engine/resources/base_spec.rb +246 -0
- data/spec/models/scim_engine/resources/base_validation_spec.rb +61 -0
- data/spec/models/scim_engine/resources/user_spec.rb +55 -0
- data/spec/models/scim_engine/schema/attribute_spec.rb +80 -0
- data/spec/models/scim_engine/schema/base_spec.rb +19 -0
- data/spec/models/scim_engine/schema/group_spec.rb +66 -0
- data/spec/models/scim_engine/schema/user_spec.rb +140 -0
- data/spec/rails_helper.rb +57 -0
- data/spec/spec_helper.rb +99 -0
- metadata +246 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 84aac5eb3121ba10036412a4c5d8ae959cf8fc99
|
4
|
+
data.tar.gz: f9c50a53333b47f4e6961dfa2a9fe256d2f207c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0bbf31701459d913599fdd83dd90b0120e19fc3c1cbff4133070293d93191044fb82fd2b2df79257544ee24618704d90946ebfea69ea632f243e30af0965584c
|
7
|
+
data.tar.gz: a53d3895e691a9aaa7ae5b4adfd7755d430c129b36b820c91a48568b03548a84e1b4673ab207dfae95b78ea03fd15c731369dda4a2c9bcc95c181e881cde6bdd
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ScimEngine'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
class ApplicationController < ActionController::Base
|
3
|
+
before_action :require_scim
|
4
|
+
before_action :authenticate
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def authenticated?
|
9
|
+
authenticate_with_http_basic do |name, password|
|
10
|
+
name == ScimEngine::Engine.username && password == ScimEngine::Engine.password
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate
|
15
|
+
handle_scim_error(ScimEngine::AuthenticationError.new) unless authenticated?
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle_resource_not_found(exception)
|
19
|
+
handle_scim_error(NotFoundError.new(params[:id]))
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_record_invalid(error_message)
|
23
|
+
handle_scim_error(ErrorResponse.new(status: 400, detail: "Operation failed since record has become invalid: #{error_message}"))
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_scim_error(error_response)
|
27
|
+
render json: error_response, status: error_response.status
|
28
|
+
end
|
29
|
+
|
30
|
+
def require_scim
|
31
|
+
unless request.format == :scim
|
32
|
+
handle_scim_error(ErrorResponse.new(status: 406, detail: "Only #{Mime::Type.lookup_by_extension(:scim)} type is accepted."))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_dependency "scim_engine/application_controller"
|
2
|
+
|
3
|
+
module ScimEngine
|
4
|
+
class ResourceTypesController < ApplicationController
|
5
|
+
def index
|
6
|
+
resource_types = ScimEngine::Engine.resources.map do |resource|
|
7
|
+
resource.resource_type(scim_resource_type_url(name: resource.resource_type_id))
|
8
|
+
end
|
9
|
+
|
10
|
+
render json: resource_types
|
11
|
+
end
|
12
|
+
|
13
|
+
def show
|
14
|
+
resource_types = ScimEngine::Engine.resources.reduce({}) do |hash, resource|
|
15
|
+
hash[resource.resource_type_id] = resource.resource_type(scim_resource_type_url(name: resource.resource_type_id))
|
16
|
+
hash
|
17
|
+
end
|
18
|
+
|
19
|
+
render json: resource_types[params[:name]]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_dependency "scim_engine/application_controller"
|
2
|
+
|
3
|
+
module ScimEngine
|
4
|
+
class ResourcesController < ApplicationController
|
5
|
+
|
6
|
+
def show(&block)
|
7
|
+
scim_user = yield resource_params[:id]
|
8
|
+
render json: scim_user
|
9
|
+
rescue ErrorResponse => error
|
10
|
+
handle_scim_error(error)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(resource_type, &block)
|
14
|
+
if resource_params[:id].present? || resource_params[:externalId].present?
|
15
|
+
handle_scim_error(ErrorResponse.new(status: 400, detail: 'id and externalId are not valid parameters for create'))
|
16
|
+
return
|
17
|
+
end
|
18
|
+
with_scim_resource(resource_type) do |resource|
|
19
|
+
render json: yield(resource, is_create: true), status: :created
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def update(resource_type, &block)
|
24
|
+
with_scim_resource(resource_type) do |resource|
|
25
|
+
render json: yield(resource)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
if yield(resource_params[:id]) != false
|
31
|
+
head :no_content
|
32
|
+
else
|
33
|
+
handle_scim_error(ErrorResponse.new(status: 500, detail: "Failed to delete the resource with id '#{params[:id]}'. Please try again later"))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def validate_request
|
40
|
+
if request.raw_post.blank?
|
41
|
+
raise ScimEngine::ErrorResponse.new(status: 400, detail: 'must provide a request body')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def with_scim_resource(resource_type)
|
46
|
+
validate_request
|
47
|
+
begin
|
48
|
+
resource = resource_type.new(resource_params.to_h)
|
49
|
+
unless resource.valid?
|
50
|
+
raise ScimEngine::ErrorResponse.new(status: 400,
|
51
|
+
detail: "Invalid resource: #{resource.errors.full_messages.join(', ')}.",
|
52
|
+
scimType: 'invalidValue')
|
53
|
+
end
|
54
|
+
|
55
|
+
yield(resource)
|
56
|
+
rescue NoMethodError => error
|
57
|
+
Rails.logger.error error
|
58
|
+
raise ScimEngine::ErrorResponse.new(status: 400, detail: 'invalid request')
|
59
|
+
end
|
60
|
+
rescue ScimEngine::ErrorResponse => error
|
61
|
+
handle_scim_error(error)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def resource_params
|
67
|
+
params.permit!
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_dependency "scim_engine/application_controller"
|
2
|
+
|
3
|
+
module ScimEngine
|
4
|
+
class SchemasController < ApplicationController
|
5
|
+
def index
|
6
|
+
schemas = ScimEngine::Engine.schemas
|
7
|
+
schemas_by_id = schemas.reduce({}) do |hash, schema|
|
8
|
+
hash[schema.id] = schema
|
9
|
+
hash
|
10
|
+
end
|
11
|
+
|
12
|
+
render json: schemas_by_id[params[:name]] || schemas
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
class AuthenticationScheme
|
3
|
+
include ActiveModel::Model
|
4
|
+
attr_accessor :type, :name, :description
|
5
|
+
|
6
|
+
def self.basic
|
7
|
+
new type: 'httpbasic',
|
8
|
+
name: 'HTTP Basic',
|
9
|
+
description: 'Authentication scheme using the HTTP Basic Standard'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
module ComplexTypes
|
3
|
+
class Base
|
4
|
+
include ActiveModel::Model
|
5
|
+
include ScimEngine::Schema::DerivedAttributes
|
6
|
+
include ScimEngine::Errors
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
super
|
10
|
+
@errors = ActiveModel::Errors.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def as_json(options={})
|
14
|
+
options[:except] ||= ['errors']
|
15
|
+
super.except(options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
class ErrorResponse < StandardError
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor :status, :detail, :scimType
|
6
|
+
|
7
|
+
def as_json(options = {})
|
8
|
+
{'schemas': ['urn:ietf:params:scim:api:messages:2.0:Error'],
|
9
|
+
'detail': detail,
|
10
|
+
'status': "#{status}"}.merge(scimType ? {'scimType': scimType} : {})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
module Errors
|
3
|
+
def add_errors_from_hash(errors_hash, prefix: nil)
|
4
|
+
errors_hash.each_pair do |key, value|
|
5
|
+
new_key = prefix.nil? ? key : "#{prefix}.#{key}".to_sym
|
6
|
+
if value.is_a?(Array)
|
7
|
+
value.each {|error| errors.add(new_key, error)}
|
8
|
+
else
|
9
|
+
errors.add(new_key, value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
class ResourceType
|
3
|
+
include ActiveModel::Model
|
4
|
+
attr_accessor :meta, :endpoint, :schema, :schemas, :id, :name, :schemaExtensions
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
default_attributes = {
|
8
|
+
meta: Meta.new(
|
9
|
+
'resourceType': 'ResourceType'
|
10
|
+
),
|
11
|
+
schemas: ['urn:ietf:params:scim:schemas:core:2.0:ResourceType']
|
12
|
+
}
|
13
|
+
super(default_attributes.merge(attributes))
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def as_json(options = {})
|
18
|
+
without_extensions = super(except: 'schemaExtensions')
|
19
|
+
if schemaExtensions.present?
|
20
|
+
extensions = schemaExtensions.map{|extension| {"schema" => extension, "required" => false}}
|
21
|
+
without_extensions.merge('schemaExtensions' => extensions)
|
22
|
+
else
|
23
|
+
without_extensions
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
module Resources
|
3
|
+
class Base
|
4
|
+
include ActiveModel::Model
|
5
|
+
include ScimEngine::Schema::DerivedAttributes
|
6
|
+
include ScimEngine::Errors
|
7
|
+
|
8
|
+
attr_accessor :id, :externalId, :meta
|
9
|
+
attr_reader :errors
|
10
|
+
validate :validate_resource
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
flattended_attributes = flatten_extension_attributes(options)
|
14
|
+
attributes = flattended_attributes.with_indifferent_access.slice(*self.class.all_attributes)
|
15
|
+
super(attributes)
|
16
|
+
constantize_complex_types(attributes)
|
17
|
+
@errors = ActiveModel::Errors.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def flatten_extension_attributes(options)
|
21
|
+
flattened = options.dup
|
22
|
+
self.class.extended_schemas.each do |extended_schema|
|
23
|
+
if extension_attrs = flattened.delete(extended_schema.id)
|
24
|
+
flattened.merge!(extension_attrs)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
flattened
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.extend_schema(schema)
|
31
|
+
derive_attributes_from_schema(schema)
|
32
|
+
extended_schemas << schema
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.extended_schemas
|
36
|
+
@extended_schemas ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.schemas
|
40
|
+
([schema] + extended_schemas).flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.all_attributes
|
44
|
+
scim_attributes = schemas.map(&:scim_attributes).flatten.map(&:name)
|
45
|
+
scim_attributes + [:id, :externalId, :meta]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.complex_scim_attributes
|
49
|
+
schema.scim_attributes.select(&:complexType).group_by(&:name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def complex_type_from_hash(scim_attribute, attr_value)
|
53
|
+
if attr_value.is_a?(Hash)
|
54
|
+
scim_attribute.complexType.new(attr_value)
|
55
|
+
else
|
56
|
+
attr_value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def constantize_complex_types(hash)
|
61
|
+
hash.with_indifferent_access.each_pair do |attr_name, attr_value|
|
62
|
+
scim_attribute = self.class.complex_scim_attributes[attr_name].try(:first)
|
63
|
+
if scim_attribute && scim_attribute.complexType
|
64
|
+
if scim_attribute.multiValued
|
65
|
+
self.send("#{attr_name}=", attr_value.map {|attr_for_each_item| complex_type_from_hash(scim_attribute, attr_for_each_item)})
|
66
|
+
else
|
67
|
+
self.send("#{attr_name}=", complex_type_from_hash(scim_attribute, attr_value))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def as_json(options = {})
|
74
|
+
self.meta = Meta.new unless self.meta
|
75
|
+
meta.resourceType = self.class.resource_type_id
|
76
|
+
original_hash = super(options).except('errors')
|
77
|
+
original_hash.merge!("schemas" => self.class.schemas.map(&:id))
|
78
|
+
self.class.extended_schemas.each do |extension_schema|
|
79
|
+
extension_attributes = extension_schema.scim_attributes.map(&:name)
|
80
|
+
original_hash.merge!(extension_schema.id => original_hash.extract!(*extension_attributes))
|
81
|
+
end
|
82
|
+
original_hash
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.resource_type_id
|
86
|
+
name.demodulize
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.resource_type(location)
|
90
|
+
resource_type = ResourceType.new(
|
91
|
+
endpoint: endpoint,
|
92
|
+
schema: schema.id,
|
93
|
+
id: resource_type_id,
|
94
|
+
name: resource_type_id,
|
95
|
+
schemaExtensions: extended_schemas.map(&:id)
|
96
|
+
)
|
97
|
+
|
98
|
+
resource_type.meta.location = location
|
99
|
+
resource_type
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_resource
|
103
|
+
self.class.schema.valid?(self)
|
104
|
+
self.class.extended_schemas.each do |extended_schema|
|
105
|
+
extended_schema.valid?(self)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
module Schema
|
3
|
+
class Attribute
|
4
|
+
include ActiveModel::Model
|
5
|
+
include ScimEngine::Errors
|
6
|
+
attr_accessor :name, :type, :multiValued, :required, :caseExact, :mutability, :returned, :uniqueness, :subAttributes, :complexType, :canonicalValues
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
defaults = {
|
10
|
+
multiValued: false,
|
11
|
+
required: true,
|
12
|
+
caseExact: false,
|
13
|
+
mutability: 'readWrite',
|
14
|
+
uniqueness: 'none',
|
15
|
+
returned: 'default',
|
16
|
+
canonicalValues: []
|
17
|
+
}
|
18
|
+
|
19
|
+
if options[:complexType]
|
20
|
+
defaults.merge!(type: 'complex', subAttributes: options[:complexType].schema.scim_attributes)
|
21
|
+
end
|
22
|
+
|
23
|
+
super(defaults.merge(options || {}))
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?(value)
|
27
|
+
return valid_blank? if value.blank? && !value.is_a?(FalseClass)
|
28
|
+
|
29
|
+
if type == 'complex'
|
30
|
+
return all_valid?(complexType, value) if multiValued
|
31
|
+
valid_complex_type?(value)
|
32
|
+
else
|
33
|
+
valid_simple_type?(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_blank?
|
38
|
+
return true unless self.required
|
39
|
+
errors.add(self.name, "is required")
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid_complex_type?(value)
|
44
|
+
if !value.class.respond_to?(:schema) || value.class.schema != complexType.schema
|
45
|
+
errors.add(self.name, "has to follow the complexType format.")
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
value.class.schema.valid?(value)
|
49
|
+
return true if value.errors.empty?
|
50
|
+
add_errors_from_hash(value.errors.to_hash, prefix: self.name)
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid_simple_type?(value)
|
55
|
+
valid = (type == 'string' && value.is_a?(String)) ||
|
56
|
+
(type == 'boolean' && (value.is_a?(TrueClass) || value.is_a?(FalseClass))) ||
|
57
|
+
(type == 'integer' && (value.is_a?(Integer))) ||
|
58
|
+
(type == 'dateTime' && valid_date_time?(value))
|
59
|
+
errors.add(self.name, "has the wrong type. It has to be a(n) #{self.type}.") unless valid
|
60
|
+
valid
|
61
|
+
end
|
62
|
+
|
63
|
+
def valid_date_time?(value)
|
64
|
+
!!Time.iso8601(value)
|
65
|
+
rescue ArgumentError
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def all_valid?(complex_type, value)
|
70
|
+
validations = value.map {|value_in_array| valid_complex_type?(value_in_array)}
|
71
|
+
validations.all?
|
72
|
+
end
|
73
|
+
|
74
|
+
def as_json(options = {})
|
75
|
+
options[:except] ||= ['complexType']
|
76
|
+
options[:except] << 'canonicalValues' if canonicalValues.empty?
|
77
|
+
super.except(options)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
module Schema
|
3
|
+
class Base
|
4
|
+
include ActiveModel::Model
|
5
|
+
attr_accessor :id, :name, :description, :scim_attributes, :meta
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
super
|
9
|
+
@meta = Meta.new(resourceType: "Schema")
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_json(options = {})
|
13
|
+
@meta.location = ScimEngine::Engine.routes.url_helpers.scim_schemas_path(name: id)
|
14
|
+
original = super
|
15
|
+
original.merge('attributes' => original.delete('scim_attributes'))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.valid?(resource)
|
19
|
+
cloned_scim_attributes.each do |scim_attribute|
|
20
|
+
resource.add_errors_from_hash(scim_attribute.errors.to_hash) unless scim_attribute.valid?(resource.send(scim_attribute.name))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.cloned_scim_attributes
|
25
|
+
scim_attributes.map { |scim_attribute| scim_attribute.clone }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ScimEngine
|
2
|
+
module Schema
|
3
|
+
module DerivedAttributes
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
def set_schema(schema)
|
8
|
+
@schema = schema
|
9
|
+
derive_attributes_from_schema(schema)
|
10
|
+
schema
|
11
|
+
end
|
12
|
+
|
13
|
+
def derive_attributes_from_schema(schema)
|
14
|
+
attr_accessor *schema.scim_attributes.map(&:name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def schema
|
18
|
+
@schema
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|