scimitar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +16 -0
  3. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +180 -0
  4. data/app/controllers/scimitar/application_controller.rb +129 -0
  5. data/app/controllers/scimitar/resource_types_controller.rb +28 -0
  6. data/app/controllers/scimitar/resources_controller.rb +203 -0
  7. data/app/controllers/scimitar/schemas_controller.rb +16 -0
  8. data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
  9. data/app/models/scimitar/authentication_error.rb +9 -0
  10. data/app/models/scimitar/authentication_scheme.rb +18 -0
  11. data/app/models/scimitar/bulk.rb +8 -0
  12. data/app/models/scimitar/complex_types/address.rb +18 -0
  13. data/app/models/scimitar/complex_types/base.rb +41 -0
  14. data/app/models/scimitar/complex_types/email.rb +12 -0
  15. data/app/models/scimitar/complex_types/entitlement.rb +12 -0
  16. data/app/models/scimitar/complex_types/ims.rb +12 -0
  17. data/app/models/scimitar/complex_types/name.rb +12 -0
  18. data/app/models/scimitar/complex_types/phone_number.rb +12 -0
  19. data/app/models/scimitar/complex_types/photo.rb +12 -0
  20. data/app/models/scimitar/complex_types/reference_group.rb +12 -0
  21. data/app/models/scimitar/complex_types/reference_member.rb +12 -0
  22. data/app/models/scimitar/complex_types/role.rb +12 -0
  23. data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
  24. data/app/models/scimitar/engine_configuration.rb +24 -0
  25. data/app/models/scimitar/error_response.rb +20 -0
  26. data/app/models/scimitar/errors.rb +14 -0
  27. data/app/models/scimitar/filter.rb +11 -0
  28. data/app/models/scimitar/filter_error.rb +22 -0
  29. data/app/models/scimitar/invalid_syntax_error.rb +9 -0
  30. data/app/models/scimitar/lists/count.rb +64 -0
  31. data/app/models/scimitar/lists/query_parser.rb +730 -0
  32. data/app/models/scimitar/meta.rb +7 -0
  33. data/app/models/scimitar/not_found_error.rb +10 -0
  34. data/app/models/scimitar/resource_invalid_error.rb +9 -0
  35. data/app/models/scimitar/resource_type.rb +29 -0
  36. data/app/models/scimitar/resources/base.rb +159 -0
  37. data/app/models/scimitar/resources/group.rb +13 -0
  38. data/app/models/scimitar/resources/mixin.rb +964 -0
  39. data/app/models/scimitar/resources/user.rb +13 -0
  40. data/app/models/scimitar/schema/address.rb +24 -0
  41. data/app/models/scimitar/schema/attribute.rb +123 -0
  42. data/app/models/scimitar/schema/base.rb +86 -0
  43. data/app/models/scimitar/schema/derived_attributes.rb +24 -0
  44. data/app/models/scimitar/schema/email.rb +10 -0
  45. data/app/models/scimitar/schema/entitlement.rb +10 -0
  46. data/app/models/scimitar/schema/group.rb +27 -0
  47. data/app/models/scimitar/schema/ims.rb +10 -0
  48. data/app/models/scimitar/schema/name.rb +20 -0
  49. data/app/models/scimitar/schema/phone_number.rb +10 -0
  50. data/app/models/scimitar/schema/photo.rb +10 -0
  51. data/app/models/scimitar/schema/reference_group.rb +23 -0
  52. data/app/models/scimitar/schema/reference_member.rb +21 -0
  53. data/app/models/scimitar/schema/role.rb +10 -0
  54. data/app/models/scimitar/schema/user.rb +52 -0
  55. data/app/models/scimitar/schema/vdtp.rb +18 -0
  56. data/app/models/scimitar/schema/x509_certificate.rb +22 -0
  57. data/app/models/scimitar/service_provider_configuration.rb +49 -0
  58. data/app/models/scimitar/supportable.rb +14 -0
  59. data/app/views/layouts/scimitar/application.html.erb +14 -0
  60. data/config/initializers/scimitar.rb +82 -0
  61. data/config/routes.rb +6 -0
  62. data/lib/scimitar.rb +23 -0
  63. data/lib/scimitar/engine.rb +63 -0
  64. data/lib/scimitar/version.rb +13 -0
  65. data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
  66. data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
  67. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
  68. data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
  69. data/spec/apps/dummy/app/models/mock_group.rb +83 -0
  70. data/spec/apps/dummy/app/models/mock_user.rb +104 -0
  71. data/spec/apps/dummy/config/application.rb +17 -0
  72. data/spec/apps/dummy/config/boot.rb +2 -0
  73. data/spec/apps/dummy/config/environment.rb +2 -0
  74. data/spec/apps/dummy/config/environments/test.rb +15 -0
  75. data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
  76. data/spec/apps/dummy/config/initializers/scimitar.rb +14 -0
  77. data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
  78. data/spec/apps/dummy/config/routes.rb +24 -0
  79. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +15 -0
  80. data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
  81. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +8 -0
  82. data/spec/apps/dummy/db/schema.rb +42 -0
  83. data/spec/controllers/scimitar/application_controller_spec.rb +173 -0
  84. data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
  85. data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
  86. data/spec/controllers/scimitar/schemas_controller_spec.rb +75 -0
  87. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
  88. data/spec/models/scimitar/complex_types/address_spec.rb +19 -0
  89. data/spec/models/scimitar/complex_types/email_spec.rb +23 -0
  90. data/spec/models/scimitar/lists/count_spec.rb +147 -0
  91. data/spec/models/scimitar/lists/query_parser_spec.rb +763 -0
  92. data/spec/models/scimitar/resource_type_spec.rb +21 -0
  93. data/spec/models/scimitar/resources/base_spec.rb +289 -0
  94. data/spec/models/scimitar/resources/base_validation_spec.rb +61 -0
  95. data/spec/models/scimitar/resources/mixin_spec.rb +2127 -0
  96. data/spec/models/scimitar/resources/user_spec.rb +55 -0
  97. data/spec/models/scimitar/schema/attribute_spec.rb +80 -0
  98. data/spec/models/scimitar/schema/base_spec.rb +64 -0
  99. data/spec/models/scimitar/schema/group_spec.rb +87 -0
  100. data/spec/models/scimitar/schema/user_spec.rb +710 -0
  101. data/spec/requests/active_record_backed_resources_controller_spec.rb +569 -0
  102. data/spec/requests/application_controller_spec.rb +49 -0
  103. data/spec/requests/controller_configuration_spec.rb +17 -0
  104. data/spec/requests/engine_spec.rb +20 -0
  105. data/spec/spec_helper.rb +66 -0
  106. metadata +315 -0
@@ -0,0 +1,13 @@
1
+ class MockUsersController < Scimitar::ActiveRecordBackedResourcesController
2
+
3
+ protected
4
+
5
+ def storage_class
6
+ MockUser
7
+ end
8
+
9
+ def storage_scope
10
+ MockUser.all
11
+ end
12
+
13
+ end
@@ -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,2 @@
1
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../../Gemfile', __FILE__)
2
+ require 'bundler/setup'
@@ -0,0 +1,2 @@
1
+ require_relative 'application'
2
+ Rails.application.initialize!
@@ -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,3 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Rails.application.config.action_dispatch.cookies_serializer = :json
@@ -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,3 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Rails.application.config.session_store :cookie_store, key: '_dummy_session'
@@ -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,10 @@
1
+ class CreateMockGroups < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :mock_groups do |t|
4
+ t.text :scim_uid
5
+ t.text :display_name
6
+
7
+ t.references :parent
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ class CreateJoinTableMockGroupsMockUsers < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_join_table :mock_groups, :mock_users do |t|
4
+ t.index [:mock_group_id, :mock_user_id]
5
+ t.index [:mock_user_id, :mock_group_id]
6
+ end
7
+ end
8
+ 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