scimitar 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.
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