scim_engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
module Schema
|
|
3
|
+
class Email < Base
|
|
4
|
+
def self.scim_attributes
|
|
5
|
+
@scim_attributes ||= [
|
|
6
|
+
Attribute.new(name: 'value', type: 'string'),
|
|
7
|
+
Attribute.new(name: 'primary', type: 'boolean', required: false),
|
|
8
|
+
Attribute.new(name: 'type', type: 'string', required: false)
|
|
9
|
+
]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
module Schema
|
|
3
|
+
class Group < Base
|
|
4
|
+
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
super(name: 'Group',
|
|
7
|
+
id: self.class.id,
|
|
8
|
+
description: 'Represents a Group',
|
|
9
|
+
scim_attributes: self.class.scim_attributes)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.id
|
|
13
|
+
'urn:ietf:params:scim:schemas:core:2.0:Group'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.scim_attributes
|
|
17
|
+
[
|
|
18
|
+
Attribute.new(name: 'displayName', type: 'string'),
|
|
19
|
+
Attribute.new(name: 'members', multiValued: true, complexType: ScimEngine::ComplexTypes::Reference, mutability: 'readOnly', required: false)
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
module Schema
|
|
3
|
+
class Name < Base
|
|
4
|
+
|
|
5
|
+
def self.scim_attributes
|
|
6
|
+
@scim_attributes ||= [
|
|
7
|
+
Attribute.new(name: 'familyName', type: 'string'),
|
|
8
|
+
Attribute.new(name: 'givenName', type: 'string'),
|
|
9
|
+
Attribute.new(name: 'formatted', type: 'string', required: false)
|
|
10
|
+
]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
module Schema
|
|
3
|
+
class Reference < Base
|
|
4
|
+
def self.scim_attributes
|
|
5
|
+
@scim_attributes ||= [
|
|
6
|
+
Attribute.new(name: 'value', type: 'string', mutability: 'readOnly'),
|
|
7
|
+
Attribute.new(name: 'display', type: 'string', mutability: 'readOnly', required: false)
|
|
8
|
+
]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
module Schema
|
|
3
|
+
class User < Base
|
|
4
|
+
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
super(name: 'User',
|
|
7
|
+
id: self.class.id,
|
|
8
|
+
description: 'Represents a User',
|
|
9
|
+
scim_attributes: self.class.scim_attributes)
|
|
10
|
+
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.id
|
|
14
|
+
'urn:ietf:params:scim:schemas:core:2.0:User'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.scim_attributes
|
|
18
|
+
[
|
|
19
|
+
Attribute.new(name: 'userName', type: 'string', uniqueness: 'server'),
|
|
20
|
+
Attribute.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name),
|
|
21
|
+
Attribute.new(name: 'emails', multiValued: true, complexType: ScimEngine::ComplexTypes::Email),
|
|
22
|
+
Attribute.new(name: 'groups', multiValued: true, mutability: 'immutable', complexType: ScimEngine::ComplexTypes::Reference)
|
|
23
|
+
]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
class ServiceProviderConfiguration
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
|
|
5
|
+
attr_accessor :patch, :bulk, :filter, :changePassword,
|
|
6
|
+
:sort, :etag, :authenticationSchemes,
|
|
7
|
+
:schemas, :meta
|
|
8
|
+
|
|
9
|
+
def initialize(attributes = {})
|
|
10
|
+
defaults = {
|
|
11
|
+
bulk: Supportable.unsupported,
|
|
12
|
+
patch: Supportable.unsupported,
|
|
13
|
+
filter: Supportable.unsupported,
|
|
14
|
+
changePassword: Supportable.unsupported,
|
|
15
|
+
sort: Supportable.unsupported,
|
|
16
|
+
etag: Supportable.unsupported,
|
|
17
|
+
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
|
18
|
+
meta: Meta.new(
|
|
19
|
+
resourceType: 'ServiceProviderConfig',
|
|
20
|
+
created: Time.now,
|
|
21
|
+
lastModified: Time.now,
|
|
22
|
+
version: '1'
|
|
23
|
+
),
|
|
24
|
+
authenticationSchemes: [AuthenticationScheme.basic]
|
|
25
|
+
}
|
|
26
|
+
super(defaults.merge(attributes))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>ScimEngine</title>
|
|
5
|
+
<%= stylesheet_link_tag "scim_engine/application", media: "all" %>
|
|
6
|
+
<%= javascript_include_tag "scim_engine/application" %>
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
|
|
11
|
+
<%= yield %>
|
|
12
|
+
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
ScimEngine::Engine.routes.draw do
|
|
2
|
+
get 'ServiceProviderConfig', to: 'service_provider_configurations#show'
|
|
3
|
+
get 'ResourceTypes', to: 'resource_types#index'
|
|
4
|
+
get 'ResourceTypes/:name', to: 'resource_types#show', as: :scim_resource_type
|
|
5
|
+
get 'Schemas', to: 'schemas#index', as: :scim_schemas
|
|
6
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module ScimEngine
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace ScimEngine
|
|
4
|
+
|
|
5
|
+
Mime::Type.register "application/scim+json",:scim
|
|
6
|
+
|
|
7
|
+
ActionDispatch::Request.parameter_parsers[Mime::Type.lookup('application/scim+json').symbol] = lambda do |body|
|
|
8
|
+
JSON.parse(body)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
mattr_accessor :username
|
|
12
|
+
mattr_accessor :password
|
|
13
|
+
|
|
14
|
+
def self.resources
|
|
15
|
+
default_resources + custom_resources
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.add_custom_resource(resource)
|
|
19
|
+
custom_resources << resource
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.custom_resources
|
|
23
|
+
@custom_resources ||= []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.default_resources
|
|
27
|
+
[ Resources::User, Resources::Group ]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.schemas
|
|
31
|
+
resources.map(&:schemas).flatten.uniq.map(&:new)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/scim_engine.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "scim_engine/engine"
|
|
2
|
+
|
|
3
|
+
module ScimEngine
|
|
4
|
+
def self.service_provider_configuration=(custom_configuration)
|
|
5
|
+
@service_provider_configuration = custom_configuration
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.service_provider_configuration(location:)
|
|
9
|
+
@service_provider_configuration ||= ServiceProviderConfiguration.new
|
|
10
|
+
@service_provider_configuration.meta.location = location
|
|
11
|
+
@service_provider_configuration
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe ScimEngine::ApplicationController do
|
|
4
|
+
|
|
5
|
+
context 'custom authentication' do
|
|
6
|
+
before do
|
|
7
|
+
allow(ScimEngine::Engine).to receive(:username) { 'A' }
|
|
8
|
+
allow(ScimEngine::Engine).to receive(:password) { 'B' }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
controller do
|
|
12
|
+
rescue_from StandardError, with: :handle_resource_not_found
|
|
13
|
+
|
|
14
|
+
def index
|
|
15
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'renders success when valid creds are given' do
|
|
20
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', 'B')
|
|
21
|
+
|
|
22
|
+
get :index, params: { format: :scim }
|
|
23
|
+
expect(response).to be_ok
|
|
24
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'renders failure with bad password' do
|
|
28
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', 'C')
|
|
29
|
+
|
|
30
|
+
get :index, params: { format: :scim }
|
|
31
|
+
expect(response).not_to be_ok
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'renders failure with bad user name' do
|
|
35
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('C', 'B')
|
|
36
|
+
|
|
37
|
+
get :index, params: { format: :scim }
|
|
38
|
+
expect(response).not_to be_ok
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
it 'renders failure with bad user name and password' do
|
|
43
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('C', 'D')
|
|
44
|
+
|
|
45
|
+
get :index, params: { format: :scim }
|
|
46
|
+
expect(response).not_to be_ok
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
context 'authenticated' do
|
|
55
|
+
controller do
|
|
56
|
+
rescue_from StandardError, with: :handle_resource_not_found
|
|
57
|
+
|
|
58
|
+
def index
|
|
59
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def authenticated?
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe 'authenticate' do
|
|
68
|
+
it 'renders index if authenticated' do
|
|
69
|
+
get :index, params: { format: :scim }
|
|
70
|
+
expect(response).to be_ok
|
|
71
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'renders not authorized response if not authenticated' do
|
|
75
|
+
allow(controller).to receive(:authenticated?) { false }
|
|
76
|
+
get :index, params: { format: :scim }
|
|
77
|
+
expect(response).to have_http_status(:unauthorized)
|
|
78
|
+
parsed_body = JSON.parse(response.body)
|
|
79
|
+
expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
|
|
80
|
+
expect(parsed_body).to include('detail' => 'Requires authentication')
|
|
81
|
+
expect(parsed_body).to include('status' => '401')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'renders resource not found response when resource cannot be found for the given id' do
|
|
85
|
+
allow(controller).to receive(:index).and_raise(StandardError)
|
|
86
|
+
get :index, params: { id: 10, format: :scim }
|
|
87
|
+
expect(response).to have_http_status(:not_found)
|
|
88
|
+
parsed_body = JSON.parse(response.body)
|
|
89
|
+
expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
|
|
90
|
+
expect(parsed_body).to include('detail' => 'Resource 10 not found')
|
|
91
|
+
expect(parsed_body).to include('status' => '404')
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe 'require_scim' do
|
|
96
|
+
it 'renders not acceptable if the request does not use scim type' do
|
|
97
|
+
get :index
|
|
98
|
+
expect(response).to have_http_status(:not_acceptable)
|
|
99
|
+
|
|
100
|
+
expect(JSON.parse(response.body)['detail']).to eql('Only application/scim+json type is accepted.')
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe ScimEngine::ResourceTypesController do
|
|
4
|
+
routes { ScimEngine::Engine.routes }
|
|
5
|
+
|
|
6
|
+
before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
|
|
7
|
+
|
|
8
|
+
describe 'GET index' do
|
|
9
|
+
it 'renders the resource type for user' do
|
|
10
|
+
get :index, format: :scim
|
|
11
|
+
response_hash = JSON.parse(response.body)
|
|
12
|
+
expected_response = [ ScimEngine::Resources::User.resource_type(scim_resource_type_url(name: 'User')),
|
|
13
|
+
ScimEngine::Resources::Group.resource_type(scim_resource_type_url(name: 'Group'))
|
|
14
|
+
].to_json
|
|
15
|
+
|
|
16
|
+
response_hash = JSON.parse(response.body)
|
|
17
|
+
expect(response_hash).to eql(JSON.parse(expected_response))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'renders custom resource types' do
|
|
21
|
+
custom_resource = Class.new(ScimEngine::Resources::Base) do
|
|
22
|
+
set_schema ScimEngine::Schema::User
|
|
23
|
+
|
|
24
|
+
def self.endpoint
|
|
25
|
+
"/Gaga"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.resource_type_id
|
|
29
|
+
'Gaga'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
allow(ScimEngine::Engine).to receive(:custom_resources) {[ custom_resource ]}
|
|
34
|
+
|
|
35
|
+
get :index, params: { format: :scim }
|
|
36
|
+
response_hash = JSON.parse(response.body)
|
|
37
|
+
expect(response_hash.size).to eql(3)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe 'GET show' do
|
|
42
|
+
it 'renders the resource type for user' do
|
|
43
|
+
get :show, params: { name: 'User', format: :scim }
|
|
44
|
+
response_hash = JSON.parse(response.body)
|
|
45
|
+
expected_response = ScimEngine::Resources::User.resource_type(scim_resource_type_url(name: 'User')).to_json
|
|
46
|
+
expect(response_hash).to eql(JSON.parse(expected_response))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'renders the resource type for group' do
|
|
50
|
+
get :show, params: { name: 'Group', format: :scim }
|
|
51
|
+
response_hash = JSON.parse(response.body)
|
|
52
|
+
expected_response = ScimEngine::Resources::Group.resource_type(scim_resource_type_url(name: 'Group')).to_json
|
|
53
|
+
expect(response_hash).to eql(JSON.parse(expected_response))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'renders custom resource type' do
|
|
57
|
+
custom_resource = Class.new(ScimEngine::Resources::Base) do
|
|
58
|
+
set_schema ScimEngine::Schema::User
|
|
59
|
+
|
|
60
|
+
def self.endpoint
|
|
61
|
+
"/Gaga"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.resource_type_id
|
|
65
|
+
'Gaga'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
allow(ScimEngine::Engine).to receive(:custom_resources) {[ custom_resource ]}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
get :show, params: { name: 'Gaga', format: :scim }
|
|
73
|
+
response_hash = JSON.parse(response.body)
|
|
74
|
+
expected_response = custom_resource.resource_type(scim_resource_type_url(name: 'Gaga')).to_json
|
|
75
|
+
expect(response_hash).to eql(JSON.parse(expected_response))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe ScimEngine::ResourcesController do
|
|
4
|
+
|
|
5
|
+
before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
|
|
6
|
+
|
|
7
|
+
controller do
|
|
8
|
+
def show
|
|
9
|
+
super do |id|
|
|
10
|
+
ScimEngine::Resources::Group.new(id: id)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
super(ScimEngine::Resources::Group) do |resource|
|
|
16
|
+
resource
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update
|
|
21
|
+
super(ScimEngine::Resources::Group) do |resource|
|
|
22
|
+
resource
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def destroy
|
|
27
|
+
super do |id|
|
|
28
|
+
successful_delete?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def successful_delete?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'GET show' do
|
|
38
|
+
it 'renders the resource' do
|
|
39
|
+
get :show, params: { id: '10', format: :scim }
|
|
40
|
+
|
|
41
|
+
expect(response).to be_ok
|
|
42
|
+
expect(JSON.parse(response.body)).to include('id' => '10')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'POST create' do
|
|
47
|
+
it 'returns error if body is missing' do
|
|
48
|
+
post :create, params: { format: :scim }
|
|
49
|
+
expect(response.status).to eql(400)
|
|
50
|
+
expect(JSON.parse(response.body)['detail']).to eql('must provide a request body')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'works if the request is valid' do
|
|
54
|
+
post :create, params: { displayName: 'Sauron biz', format: :scim }
|
|
55
|
+
expect(response).to have_http_status(:created)
|
|
56
|
+
expect(JSON.parse(response.body)['displayName']).to eql('Sauron biz')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'renders error if resource object cannot be built from the params' do
|
|
60
|
+
put :update, params: { id: 'group-id', name: {email: 'a@b.com'}, format: :scim }
|
|
61
|
+
expect(response.status).to eql(400)
|
|
62
|
+
expect(JSON.parse(response.body)['detail']).to match(/^Invalid/)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'renders application side error' do
|
|
66
|
+
allow_any_instance_of(ScimEngine::Resources::Group).to receive(:to_json).and_raise(ScimEngine::ErrorResponse.new(status: 400, detail: 'gaga'))
|
|
67
|
+
put :update, params: { id: 'group-id', displayName: 'invalid name', format: :scim }
|
|
68
|
+
expect(response.status).to eql(400)
|
|
69
|
+
expect(JSON.parse(response.body)['detail']).to eql('gaga')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'renders error if id is provided' do
|
|
73
|
+
post :create, params: { id: 'some-id', displayName: 'sauron', format: :scim }
|
|
74
|
+
|
|
75
|
+
expect(response).to have_http_status(:bad_request)
|
|
76
|
+
|
|
77
|
+
parsed_response = JSON.parse(response.body)
|
|
78
|
+
expect(parsed_response['detail']).to start_with('id and externalId are not valid parameters for create')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'renders error if externalId is provided' do
|
|
82
|
+
post :create, params: { externalId: 'some-id', displayName: 'sauron', format: :scim }
|
|
83
|
+
|
|
84
|
+
expect(response).to have_http_status(:bad_request)
|
|
85
|
+
|
|
86
|
+
parsed_response = JSON.parse(response.body)
|
|
87
|
+
expect(parsed_response['detail']).to start_with('id and externalId are not valid parameters for create')
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe 'PUT update' do
|
|
92
|
+
it 'returns error if body is missing' do
|
|
93
|
+
put :update, params: { id: 'group-id', format: :scim }
|
|
94
|
+
expect(response.status).to eql(400)
|
|
95
|
+
expect(JSON.parse(response.body)['detail']).to eql('must provide a request body')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'works if the request is valid' do
|
|
99
|
+
put :update, params: { id: 'group-id', displayName: 'sauron', format: :scim }
|
|
100
|
+
expect(response.status).to eql(200)
|
|
101
|
+
expect(JSON.parse(response.body)['displayName']).to eql('sauron')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'renders error if resource object cannot be built from the params' do
|
|
105
|
+
put :update, params: { id: 'group-id', name: {email: 'a@b.com'}, format: :scim }
|
|
106
|
+
expect(response.status).to eql(400)
|
|
107
|
+
expect(JSON.parse(response.body)['detail']).to match(/^Invalid/)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'renders application side error' do
|
|
111
|
+
allow_any_instance_of(ScimEngine::Resources::Group).to receive(:to_json).and_raise(ScimEngine::ErrorResponse.new(status: 400, detail: 'gaga'))
|
|
112
|
+
put :update, params: { id: 'group-id', displayName: 'invalid name', format: :scim }
|
|
113
|
+
expect(response.status).to eql(400)
|
|
114
|
+
expect(JSON.parse(response.body)['detail']).to eql('gaga')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe 'DELETE destroy' do
|
|
120
|
+
it 'returns an empty response with no content status if deletion is successful' do
|
|
121
|
+
delete :destroy, params: { id: 'group-id', format: :scim }
|
|
122
|
+
expect(response).to have_http_status(:no_content)
|
|
123
|
+
expect(response.body).to be_empty
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'renders error if deletion fails' do
|
|
127
|
+
allow(controller).to receive(:successful_delete?).and_return(false)
|
|
128
|
+
delete :destroy, params: { id: 'group-id', format: :scim }
|
|
129
|
+
expect(response).to have_http_status(:internal_server_error)
|
|
130
|
+
parsed_response = JSON.parse(response.body)
|
|
131
|
+
expect(parsed_response['detail']).to eql("Failed to delete the resource with id 'group-id'. Please try again later")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe ScimEngine::SchemasController do
|
|
4
|
+
|
|
5
|
+
before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
|
|
6
|
+
|
|
7
|
+
controller do
|
|
8
|
+
def index
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
describe '#index' do
|
|
13
|
+
it 'returns a collection of supported schemas' do
|
|
14
|
+
get :index, params: { format: :scim }
|
|
15
|
+
expect(response).to be_ok
|
|
16
|
+
parsed_body = JSON.parse(response.body)
|
|
17
|
+
expect(parsed_body.length).to eql(2)
|
|
18
|
+
schema_names = parsed_body.map {|schema| schema['name']}
|
|
19
|
+
expect(schema_names).to match_array(['User', 'Group'])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns only the User schema when its id is provided' do
|
|
23
|
+
get :index, params: { name: ScimEngine::Schema::User.id, format: :scim }
|
|
24
|
+
expect(response).to be_ok
|
|
25
|
+
parsed_body = JSON.parse(response.body)
|
|
26
|
+
expect(parsed_body['name']).to eql('User')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'returns only the Group schema when its id is provided' do
|
|
30
|
+
get :index, params: { name: ScimEngine::Schema::Group.id, format: :scim }
|
|
31
|
+
expect(response).to be_ok
|
|
32
|
+
parsed_body = JSON.parse(response.body)
|
|
33
|
+
expect(parsed_body['name']).to eql('Group')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'returns only the License schemas when its id is provided' do
|
|
37
|
+
license_schema = Class.new(ScimEngine::Schema::Base) do
|
|
38
|
+
def initialize(options = {})
|
|
39
|
+
super(name: 'License',
|
|
40
|
+
id: self.class.id,
|
|
41
|
+
description: 'Represents a License')
|
|
42
|
+
end
|
|
43
|
+
def self.id
|
|
44
|
+
'License'
|
|
45
|
+
end
|
|
46
|
+
def self.scim_attributes
|
|
47
|
+
[]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
license_resource = Class.new(ScimEngine::Resources::Base) do
|
|
52
|
+
set_schema license_schema
|
|
53
|
+
def self.endopint
|
|
54
|
+
'/Gaga'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
allow(ScimEngine::Engine).to receive(:custom_resources) {[license_resource]}
|
|
59
|
+
get :index, params: { name: license_schema.id, format: :scim }
|
|
60
|
+
expect(response).to be_ok
|
|
61
|
+
parsed_body = JSON.parse(response.body)
|
|
62
|
+
expect(parsed_body['name']).to eql('License')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe ScimEngine::ServiceProviderConfigurationsController do
|
|
4
|
+
|
|
5
|
+
before(:each) { allow(controller).to receive(:authenticated?).and_return(true) }
|
|
6
|
+
|
|
7
|
+
controller do
|
|
8
|
+
def show
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
describe '#show' do
|
|
13
|
+
it 'renders the servive provider configurations' do
|
|
14
|
+
get :show, params: { id: 'fake', format: :scim }
|
|
15
|
+
|
|
16
|
+
expect(response).to be_ok
|
|
17
|
+
expect(JSON.parse(response.body)).to include('patch' => {'supported' => false})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
== README
|
|
2
|
+
|
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
|
4
|
+
application up and running.
|
|
5
|
+
|
|
6
|
+
Things you may want to cover:
|
|
7
|
+
|
|
8
|
+
* Ruby version
|
|
9
|
+
|
|
10
|
+
* System dependencies
|
|
11
|
+
|
|
12
|
+
* Configuration
|
|
13
|
+
|
|
14
|
+
* Database creation
|
|
15
|
+
|
|
16
|
+
* Database initialization
|
|
17
|
+
|
|
18
|
+
* How to run the test suite
|
|
19
|
+
|
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
|
21
|
+
|
|
22
|
+
* Deployment instructions
|
|
23
|
+
|
|
24
|
+
* ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
|
28
|
+
<tt>rake doc:app</tt>.
|
data/spec/dummy/Rakefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
|
2
|
+
// listed below.
|
|
3
|
+
//
|
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
|
6
|
+
//
|
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
|
8
|
+
// compiled file.
|
|
9
|
+
//
|
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
|
11
|
+
// about supported directives.
|
|
12
|
+
//
|
|
13
|
+
//= require_tree .
|