scimitar 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/Rakefile +16 -0
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +180 -0
- data/app/controllers/scimitar/application_controller.rb +129 -0
- data/app/controllers/scimitar/resource_types_controller.rb +28 -0
- data/app/controllers/scimitar/resources_controller.rb +203 -0
- data/app/controllers/scimitar/schemas_controller.rb +16 -0
- data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
- data/app/models/scimitar/authentication_error.rb +9 -0
- data/app/models/scimitar/authentication_scheme.rb +18 -0
- data/app/models/scimitar/bulk.rb +8 -0
- data/app/models/scimitar/complex_types/address.rb +18 -0
- data/app/models/scimitar/complex_types/base.rb +41 -0
- data/app/models/scimitar/complex_types/email.rb +12 -0
- data/app/models/scimitar/complex_types/entitlement.rb +12 -0
- data/app/models/scimitar/complex_types/ims.rb +12 -0
- data/app/models/scimitar/complex_types/name.rb +12 -0
- data/app/models/scimitar/complex_types/phone_number.rb +12 -0
- data/app/models/scimitar/complex_types/photo.rb +12 -0
- data/app/models/scimitar/complex_types/reference_group.rb +12 -0
- data/app/models/scimitar/complex_types/reference_member.rb +12 -0
- data/app/models/scimitar/complex_types/role.rb +12 -0
- data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
- data/app/models/scimitar/engine_configuration.rb +24 -0
- data/app/models/scimitar/error_response.rb +20 -0
- data/app/models/scimitar/errors.rb +14 -0
- data/app/models/scimitar/filter.rb +11 -0
- data/app/models/scimitar/filter_error.rb +22 -0
- data/app/models/scimitar/invalid_syntax_error.rb +9 -0
- data/app/models/scimitar/lists/count.rb +64 -0
- data/app/models/scimitar/lists/query_parser.rb +730 -0
- data/app/models/scimitar/meta.rb +7 -0
- data/app/models/scimitar/not_found_error.rb +10 -0
- data/app/models/scimitar/resource_invalid_error.rb +9 -0
- data/app/models/scimitar/resource_type.rb +29 -0
- data/app/models/scimitar/resources/base.rb +159 -0
- data/app/models/scimitar/resources/group.rb +13 -0
- data/app/models/scimitar/resources/mixin.rb +964 -0
- data/app/models/scimitar/resources/user.rb +13 -0
- data/app/models/scimitar/schema/address.rb +24 -0
- data/app/models/scimitar/schema/attribute.rb +123 -0
- data/app/models/scimitar/schema/base.rb +86 -0
- data/app/models/scimitar/schema/derived_attributes.rb +24 -0
- data/app/models/scimitar/schema/email.rb +10 -0
- data/app/models/scimitar/schema/entitlement.rb +10 -0
- data/app/models/scimitar/schema/group.rb +27 -0
- data/app/models/scimitar/schema/ims.rb +10 -0
- data/app/models/scimitar/schema/name.rb +20 -0
- data/app/models/scimitar/schema/phone_number.rb +10 -0
- data/app/models/scimitar/schema/photo.rb +10 -0
- data/app/models/scimitar/schema/reference_group.rb +23 -0
- data/app/models/scimitar/schema/reference_member.rb +21 -0
- data/app/models/scimitar/schema/role.rb +10 -0
- data/app/models/scimitar/schema/user.rb +52 -0
- data/app/models/scimitar/schema/vdtp.rb +18 -0
- data/app/models/scimitar/schema/x509_certificate.rb +22 -0
- data/app/models/scimitar/service_provider_configuration.rb +49 -0
- data/app/models/scimitar/supportable.rb +14 -0
- data/app/views/layouts/scimitar/application.html.erb +14 -0
- data/config/initializers/scimitar.rb +82 -0
- data/config/routes.rb +6 -0
- data/lib/scimitar.rb +23 -0
- data/lib/scimitar/engine.rb +63 -0
- data/lib/scimitar/version.rb +13 -0
- data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
- data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
- data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
- data/spec/apps/dummy/app/models/mock_group.rb +83 -0
- data/spec/apps/dummy/app/models/mock_user.rb +104 -0
- data/spec/apps/dummy/config/application.rb +17 -0
- data/spec/apps/dummy/config/boot.rb +2 -0
- data/spec/apps/dummy/config/environment.rb +2 -0
- data/spec/apps/dummy/config/environments/test.rb +15 -0
- data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/apps/dummy/config/initializers/scimitar.rb +14 -0
- data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
- data/spec/apps/dummy/config/routes.rb +24 -0
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +15 -0
- data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +8 -0
- data/spec/apps/dummy/db/schema.rb +42 -0
- data/spec/controllers/scimitar/application_controller_spec.rb +173 -0
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
- data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
- data/spec/controllers/scimitar/schemas_controller_spec.rb +75 -0
- data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
- data/spec/models/scimitar/complex_types/address_spec.rb +19 -0
- data/spec/models/scimitar/complex_types/email_spec.rb +23 -0
- data/spec/models/scimitar/lists/count_spec.rb +147 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +763 -0
- data/spec/models/scimitar/resource_type_spec.rb +21 -0
- data/spec/models/scimitar/resources/base_spec.rb +289 -0
- data/spec/models/scimitar/resources/base_validation_spec.rb +61 -0
- data/spec/models/scimitar/resources/mixin_spec.rb +2127 -0
- data/spec/models/scimitar/resources/user_spec.rb +55 -0
- data/spec/models/scimitar/schema/attribute_spec.rb +80 -0
- data/spec/models/scimitar/schema/base_spec.rb +64 -0
- data/spec/models/scimitar/schema/group_spec.rb +87 -0
- data/spec/models/scimitar/schema/user_spec.rb +710 -0
- data/spec/requests/active_record_backed_resources_controller_spec.rb +569 -0
- data/spec/requests/application_controller_spec.rb +49 -0
- data/spec/requests/controller_configuration_spec.rb +17 -0
- data/spec/requests/engine_spec.rb +20 -0
- data/spec/spec_helper.rb +66 -0
- metadata +315 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
class MockGroup < ActiveRecord::Base
|
2
|
+
|
3
|
+
# ===========================================================================
|
4
|
+
# TEST ATTRIBUTES - see db/migrate/20210308020313_create_mock_groups.rb etc.
|
5
|
+
# ===========================================================================
|
6
|
+
|
7
|
+
READWRITE_ATTRS = %w{
|
8
|
+
id
|
9
|
+
scim_uid
|
10
|
+
display_name
|
11
|
+
scim_users_and_groups
|
12
|
+
}
|
13
|
+
|
14
|
+
has_and_belongs_to_many :mock_users
|
15
|
+
|
16
|
+
has_many :child_mock_groups, class_name: 'MockGroup', foreign_key: 'parent_id'
|
17
|
+
|
18
|
+
# ===========================================================================
|
19
|
+
# SCIM ADAPTER ACCESSORS
|
20
|
+
#
|
21
|
+
# Groups in SCIM can contain users or other groups. That's why the :find_with
|
22
|
+
# key in the Hash returned by ::scim_attributes_map has to check the type of
|
23
|
+
# thing it needs to find. Since the mappings only support a single read/write
|
24
|
+
# accessor, we need custom accessors to do what SCIM is expecting by turning
|
25
|
+
# the Rails associations to/from mixed, flat arrays of mock users and groups.
|
26
|
+
# ===========================================================================
|
27
|
+
|
28
|
+
def scim_users_and_groups
|
29
|
+
self.mock_users.to_a + self.child_mock_groups.to_a
|
30
|
+
end
|
31
|
+
|
32
|
+
def scim_users_and_groups=(mixed_array)
|
33
|
+
self.mock_users = mixed_array.select { |item| item.is_a?(MockUser) }
|
34
|
+
self.child_mock_groups = mixed_array.select { |item| item.is_a?(MockGroup) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# ===========================================================================
|
38
|
+
# SCIM MIXIN AND REQUIRED METHODS
|
39
|
+
# ===========================================================================
|
40
|
+
|
41
|
+
def self.scim_resource_type
|
42
|
+
return Scimitar::Resources::Group
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.scim_attributes_map
|
46
|
+
return {
|
47
|
+
id: :id,
|
48
|
+
externalId: :scim_uid,
|
49
|
+
displayName: :display_name,
|
50
|
+
members: [ # NB read-write, though individual items' attributes are immutable
|
51
|
+
list: :scim_users_and_groups, # See adapter accessors, earlier in this file
|
52
|
+
using: {
|
53
|
+
value: :id
|
54
|
+
},
|
55
|
+
find_with: -> (scim_list_entry) {
|
56
|
+
id = scim_list_entry['value']
|
57
|
+
type = scim_list_entry['type' ] || 'User' # Some online examples omit 'type' and believe 'User' will be assumed
|
58
|
+
|
59
|
+
case type.downcase
|
60
|
+
when 'user'
|
61
|
+
MockUser.find_by_id(id)
|
62
|
+
when 'group'
|
63
|
+
MockGroup.find_by_id(id)
|
64
|
+
else
|
65
|
+
raise Scimitar::InvalidSyntaxError.new("Unrecognised type #{type.inspect}")
|
66
|
+
end
|
67
|
+
}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.scim_mutable_attributes
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.scim_queryable_attributes
|
77
|
+
return {
|
78
|
+
displayName: :display_name
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
include Scimitar::Resources::Mixin
|
83
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
class MockUser < ActiveRecord::Base
|
2
|
+
|
3
|
+
# ===========================================================================
|
4
|
+
# TEST ATTRIBUTES - see db/migrate/20210304014602_create_mock_users.rb etc.
|
5
|
+
# ===========================================================================
|
6
|
+
|
7
|
+
READWRITE_ATTRS = %w{
|
8
|
+
id
|
9
|
+
scim_uid
|
10
|
+
username
|
11
|
+
first_name
|
12
|
+
last_name
|
13
|
+
work_email_address
|
14
|
+
home_email_address
|
15
|
+
work_phone_number
|
16
|
+
}
|
17
|
+
|
18
|
+
has_and_belongs_to_many :mock_groups
|
19
|
+
|
20
|
+
# A fixed value read-only attribute, in essence.
|
21
|
+
#
|
22
|
+
def is_active
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# A test hook to force validation failures.
|
27
|
+
#
|
28
|
+
INVALID_USERNAME = 'invalid username'
|
29
|
+
validates :username, uniqueness: true, exclusion: { in: [INVALID_USERNAME] }
|
30
|
+
|
31
|
+
# ===========================================================================
|
32
|
+
# SCIM MIXIN AND REQUIRED METHODS
|
33
|
+
# ===========================================================================
|
34
|
+
|
35
|
+
def self.scim_resource_type
|
36
|
+
return Scimitar::Resources::User
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.scim_attributes_map
|
40
|
+
return {
|
41
|
+
id: :id,
|
42
|
+
externalId: :scim_uid,
|
43
|
+
userName: :username,
|
44
|
+
name: {
|
45
|
+
givenName: :first_name,
|
46
|
+
familyName: :last_name
|
47
|
+
},
|
48
|
+
emails: [
|
49
|
+
{
|
50
|
+
match: 'type',
|
51
|
+
with: 'work',
|
52
|
+
using: {
|
53
|
+
value: :work_email_address,
|
54
|
+
primary: true
|
55
|
+
}
|
56
|
+
},
|
57
|
+
{
|
58
|
+
match: 'type',
|
59
|
+
with: 'home',
|
60
|
+
using: {
|
61
|
+
value: :home_email_address,
|
62
|
+
primary: false
|
63
|
+
}
|
64
|
+
},
|
65
|
+
],
|
66
|
+
phoneNumbers: [
|
67
|
+
{
|
68
|
+
match: 'type',
|
69
|
+
with: 'work',
|
70
|
+
using: {
|
71
|
+
value: :work_phone_number,
|
72
|
+
primary: false
|
73
|
+
}
|
74
|
+
},
|
75
|
+
],
|
76
|
+
groups: [ # NB read-only, so no :find_with key
|
77
|
+
{
|
78
|
+
list: :mock_groups,
|
79
|
+
using: {
|
80
|
+
value: :id,
|
81
|
+
display: :display_name
|
82
|
+
}
|
83
|
+
}
|
84
|
+
],
|
85
|
+
active: :is_active
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.scim_mutable_attributes
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.scim_queryable_attributes
|
94
|
+
return {
|
95
|
+
'name.givenName' => { column: :first_name },
|
96
|
+
'name.familyName' => { column: :last_name },
|
97
|
+
'emails' => { columns: [ :work_email_address, :home_email_address ] },
|
98
|
+
'emails.value' => { columns: [ :work_email_address, :home_email_address ] },
|
99
|
+
'emails.type' => { ignore: true } # We can't filter on that; it'll just search all e-mails
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
include Scimitar::Resources::Mixin
|
104
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'boot'
|
2
|
+
|
3
|
+
require 'rails'
|
4
|
+
require 'active_model/railtie'
|
5
|
+
require 'active_record/railtie'
|
6
|
+
require 'action_controller/railtie'
|
7
|
+
require 'action_view/railtie'
|
8
|
+
|
9
|
+
Bundler.require(*Rails.groups)
|
10
|
+
|
11
|
+
require 'scimitar'
|
12
|
+
|
13
|
+
module Dummy
|
14
|
+
class Application < Rails::Application
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Rails.application.configure do
|
2
|
+
config.cache_classes = true
|
3
|
+
config.eager_load = false
|
4
|
+
config.serve_static_files = true
|
5
|
+
config.static_cache_control = 'public, max-age=3600'
|
6
|
+
config.consider_all_requests_local = true
|
7
|
+
|
8
|
+
config.action_dispatch.show_exceptions = false
|
9
|
+
|
10
|
+
config.action_controller.perform_caching = false
|
11
|
+
config.action_controller.allow_forgery_protection = false
|
12
|
+
|
13
|
+
config.active_support.test_order = :random
|
14
|
+
config.active_support.deprecation = :stderr
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Test app configuration.
|
2
|
+
#
|
3
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
4
|
+
|
5
|
+
application_controller_mixin: Module.new do
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
def test_hook; end
|
9
|
+
before_action :test_hook
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
})
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# This test app mounts everything at the root level, but you'd usually be doing
|
2
|
+
# more in your Rails app than just SCIM! Wrapping with 'namespace :foo do' is
|
3
|
+
# strongly recommended to avoid routing namespace collisions. See README.md for
|
4
|
+
# an example.
|
5
|
+
#
|
6
|
+
Rails.application.routes.draw do
|
7
|
+
mount Scimitar::Engine, at: '/'
|
8
|
+
|
9
|
+
get 'Users', to: 'mock_users#index'
|
10
|
+
get 'Users/:id', to: 'mock_users#show'
|
11
|
+
post 'Users', to: 'mock_users#create'
|
12
|
+
put 'Users/:id', to: 'mock_users#replace'
|
13
|
+
patch 'Users/:id', to: 'mock_users#update'
|
14
|
+
delete 'Users/:id', to: 'mock_users#destroy'
|
15
|
+
|
16
|
+
# For testing blocks passed to ActiveRecordBackedResourcesController#destroy
|
17
|
+
#
|
18
|
+
delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
|
19
|
+
|
20
|
+
# For testing environment inside Scimitar::ApplicationController subclasses.
|
21
|
+
#
|
22
|
+
get 'CustomRequestVerifiers', to: 'custom_request_verifiers#index'
|
23
|
+
post 'CustomRequestVerifiers', to: 'custom_request_verifiers#create'
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateMockUsers < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
create_table :mock_users do |t|
|
4
|
+
|
5
|
+
t.text :scim_uid
|
6
|
+
t.text :username
|
7
|
+
t.text :first_name
|
8
|
+
t.text :last_name
|
9
|
+
t.text :work_email_address
|
10
|
+
t.text :home_email_address
|
11
|
+
t.text :work_phone_number
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
4
|
+
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
10
|
+
#
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define(version: 2021_03_08_044214) do
|
14
|
+
|
15
|
+
# These are extensions that must be enabled in order to support this database
|
16
|
+
enable_extension "plpgsql"
|
17
|
+
|
18
|
+
create_table "mock_groups", force: :cascade do |t|
|
19
|
+
t.text "scim_uid"
|
20
|
+
t.text "display_name"
|
21
|
+
t.bigint "parent_id"
|
22
|
+
t.index ["parent_id"], name: "index_mock_groups_on_parent_id"
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table "mock_groups_users", id: false, force: :cascade do |t|
|
26
|
+
t.bigint "mock_group_id", null: false
|
27
|
+
t.bigint "mock_user_id", null: false
|
28
|
+
t.index ["mock_group_id", "mock_user_id"], name: "index_mock_groups_users_on_mock_group_id_and_mock_user_id"
|
29
|
+
t.index ["mock_user_id", "mock_group_id"], name: "index_mock_groups_users_on_mock_user_id_and_mock_group_id"
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table "mock_users", force: :cascade do |t|
|
33
|
+
t.text "scim_uid"
|
34
|
+
t.text "username"
|
35
|
+
t.text "first_name"
|
36
|
+
t.text "last_name"
|
37
|
+
t.text "work_email_address"
|
38
|
+
t.text "home_email_address"
|
39
|
+
t.text "work_phone_number"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Scimitar::ApplicationController do
|
4
|
+
context 'basic authentication' do
|
5
|
+
before do
|
6
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new(
|
7
|
+
basic_authenticator: Proc.new do | username, password |
|
8
|
+
username == 'A' && password == 'B'
|
9
|
+
end
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
controller do
|
14
|
+
rescue_from StandardError, with: :handle_resource_not_found
|
15
|
+
|
16
|
+
def index
|
17
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'renders success when valid creds are given' do
|
22
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', 'B')
|
23
|
+
|
24
|
+
get :index, params: { format: :scim }
|
25
|
+
expect(response).to be_ok
|
26
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
27
|
+
expect(response.headers['WWW_AUTHENTICATE']).to eql('Basic')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'renders failure with bad password' do
|
31
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', 'C')
|
32
|
+
|
33
|
+
get :index, params: { format: :scim }
|
34
|
+
expect(response).not_to be_ok
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'renders failure with bad user name' do
|
38
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('C', 'B')
|
39
|
+
|
40
|
+
get :index, params: { format: :scim }
|
41
|
+
expect(response).not_to be_ok
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'renders failure with bad user name and password' do
|
45
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('C', 'D')
|
46
|
+
|
47
|
+
get :index, params: { format: :scim }
|
48
|
+
expect(response).not_to be_ok
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'renders failure with blank password' do
|
52
|
+
request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('A', '')
|
53
|
+
|
54
|
+
get :index, params: { format: :scim }
|
55
|
+
expect(response).not_to be_ok
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'renders failure with missing header' do
|
59
|
+
get :index, params: { format: :scim }
|
60
|
+
expect(response).not_to be_ok
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'token authentication' do
|
65
|
+
before do
|
66
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new(
|
67
|
+
token_authenticator: Proc.new do | token, options |
|
68
|
+
token == 'A'
|
69
|
+
end
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
controller do
|
74
|
+
rescue_from StandardError, with: :handle_resource_not_found
|
75
|
+
|
76
|
+
def index
|
77
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'renders success when valid creds are given' do
|
82
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer A'
|
83
|
+
|
84
|
+
get :index, params: { format: :scim }
|
85
|
+
expect(response).to be_ok
|
86
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
87
|
+
expect(response.headers['WWW_AUTHENTICATE']).to eql('Bearer')
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'renders failure with bad token' do
|
91
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer Invalid'
|
92
|
+
|
93
|
+
get :index, params: { format: :scim }
|
94
|
+
expect(response).not_to be_ok
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'renders failure with blank token' do
|
98
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer'
|
99
|
+
|
100
|
+
get :index, params: { format: :scim }
|
101
|
+
expect(response).not_to be_ok
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'renders failure with missing header' do
|
105
|
+
get :index, params: { format: :scim }
|
106
|
+
expect(response).not_to be_ok
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'authenticated' do
|
111
|
+
controller do
|
112
|
+
rescue_from StandardError, with: :handle_resource_not_found
|
113
|
+
|
114
|
+
def index
|
115
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
116
|
+
end
|
117
|
+
|
118
|
+
def authenticated?
|
119
|
+
true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'authenticate' do
|
124
|
+
it 'renders index if authenticated' do
|
125
|
+
get :index, params: { format: :scim }
|
126
|
+
expect(response).to be_ok
|
127
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'renders not authorized response if not authenticated' do
|
131
|
+
allow(controller()).to receive(:authenticated?) { false }
|
132
|
+
get :index, params: { format: :scim }
|
133
|
+
expect(response).to have_http_status(:unauthorized)
|
134
|
+
parsed_body = JSON.parse(response.body)
|
135
|
+
expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
|
136
|
+
expect(parsed_body).to include('detail' => 'Requires authentication')
|
137
|
+
expect(parsed_body).to include('status' => '401')
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'renders resource not found response when resource cannot be found for the given id' do
|
141
|
+
allow(controller()).to receive(:index).and_raise(StandardError)
|
142
|
+
get :index, params: { id: 10, format: :scim }
|
143
|
+
expect(response).to have_http_status(:not_found)
|
144
|
+
parsed_body = JSON.parse(response.body)
|
145
|
+
expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
|
146
|
+
expect(parsed_body).to include('detail' => 'Resource "10" not found')
|
147
|
+
expect(parsed_body).to include('status' => '404')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'error handling' do
|
153
|
+
controller do
|
154
|
+
def index
|
155
|
+
raise 'Bang'
|
156
|
+
end
|
157
|
+
|
158
|
+
def authenticated?
|
159
|
+
true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'handles general exceptions automatically' do
|
164
|
+
get :index, params: { format: :scim }
|
165
|
+
|
166
|
+
expect(response).to have_http_status(:internal_server_error)
|
167
|
+
parsed_body = JSON.parse(response.body)
|
168
|
+
expect(parsed_body).to include('schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'])
|
169
|
+
expect(parsed_body).to include('status' => '500')
|
170
|
+
expect(parsed_body).to include('detail' => 'Bang')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|