scim_rails 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -0
  3. data/Rakefile +1 -1
  4. data/app/controllers/concerns/scim_rails/exception_handler.rb +8 -5
  5. data/app/controllers/scim_rails/application_controller.rb +20 -4
  6. data/app/controllers/scim_rails/scim_users_controller.rb +28 -10
  7. data/config/environment.rb +0 -0
  8. data/lib/generators/scim_rails/templates/initializer.rb +19 -3
  9. data/lib/scim_rails.rb +1 -0
  10. data/lib/scim_rails/config.rb +17 -5
  11. data/lib/scim_rails/encoder.rb +25 -0
  12. data/lib/scim_rails/version.rb +3 -1
  13. data/spec/controllers/scim_rails/scim_users_controller_spec.rb +144 -59
  14. data/spec/controllers/scim_rails/scim_users_request_spec.rb +41 -19
  15. data/spec/dummy/config/initializers/scim_rails_config.rb +3 -0
  16. data/spec/dummy/config/routes.rb +1 -1
  17. data/spec/dummy/db/development.sqlite3 +0 -0
  18. data/spec/dummy/db/test.sqlite3 +0 -0
  19. data/spec/dummy/log/test.log +5267 -56757
  20. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/K3/K3kRdEIVqv2uHDkXatQjmCumpOCKxtnexZuiH4Ad37A.cache +1 -0
  21. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/TH/THOPrXYljWHCQGbFjofWsaZNw7w-hfqfI3-hnxWyGas.cache +1 -0
  22. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/TW/TW6bO0RoLe0_sbLapNXYMgZbgPTnnSFdrhh6pv917KA.cache +2 -0
  23. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/kW/kWKxWPvgf53JDnfwnEOVTJYtSWS971rKq3BhmUzYaXY.cache +1 -0
  24. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/mX/mX1nlsL_SWOB4y22W5FheRX0YEONKyOY7xUeIvRiHco.cache +2 -0
  25. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pR/pRHZ5T7C7u6vDXHn5oM357U3KshRFgRMbA53zz4Azcw.cache +0 -0
  26. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/q8/q8BHffCjwsZ85QWxK1lyx5t_0jQSLlTtLGhRrwuWXGI.cache +0 -0
  27. data/spec/factories/company.rb +4 -1
  28. data/spec/lib/scim_rails/encoder_spec.rb +62 -0
  29. data/spec/support/scim_rails_config.rb +3 -0
  30. metadata +65 -15
  31. data/spec/dummy/log/development.log +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 325745eceb9f970c505d036231d3fde95674f05d220d65e02778e346013f44c1
4
- data.tar.gz: 2703cf04051e134870be167e9119ef7041b93fa5d77a5eedffb80bae2b707aff
3
+ metadata.gz: 606745240d4c119078d5694d05da63c4787c6a6f4c1a287c814fdec48e16ea92
4
+ data.tar.gz: 8b636b98654ed1520cc9d9238e657ac715b1f246d0c406a6811fc4cdb6ec5ba0
5
5
  SHA512:
6
- metadata.gz: 73975ecfb6499a9a8e155d0817d81c9f8bd1f8c96070b21172610a06a620a46e1bf446a40fcf734f065fa6a1bf17d4b74857ab7443ede69694592f622e8fb6be
7
- data.tar.gz: caaa96d37fc4a7e11077afe889b7e0ddb1e7df1518d601552ff26fd471c94e157a7b5672a3f96eb0dae5f75fc66cedbd38b4f5b09d2b5b5365d7d4388a6fc532
6
+ metadata.gz: 073b1d94a5b17e7586584b78dbcbe39d08aa319db28c83ce9a30e135ed7d33aea3e28fce838a1b36e786c0e4d3f76a3d36832461cef3bf1c811f5f78645386ce
7
+ data.tar.gz: faef4ff11f04d2586f296c5011b3e3fc1257c336682b5269b2aebfcafaaef7961eb3e3c57add0d693ed6aad22c8642854c031000c4c58d284e55b47ba1d1f83e
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![Build Status](https://travis-ci.com/lessonly/scim_rails.svg?branch=master)](https://travis-ci.com/lessonly/scim_rails)
2
+ [![Inline docs](http://inch-ci.org/github/lessonly/scim_rails.svg?branch=master)](http://inch-ci.org/github/lessonly/scim_rails)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ddfb6a891d2f0d1122ae/maintainability)](https://codeclimate.com/github/lessonly/scim_rails/maintainability)
4
+
1
5
  # ScimRails
2
6
 
3
7
  NOTE: This Gem is not yet fully SCIM complaint. It was developed with the main function of interfacing with Okta. There are features of SCIM that this Gem does not implement as described in the SCIM documentation or that have been left out completely.
@@ -78,6 +82,57 @@ When sending requests to the server the `Content-Type` should be set to `applica
78
82
 
79
83
  All responses will be sent with a `Content-Type` of `application/scim+json`.
80
84
 
85
+ #### Authentication
86
+
87
+ This gem supports both basic and OAuth bearer authentication.
88
+
89
+ ##### Basic Auth
90
+ ###### Username
91
+ The config setting `basic_auth_model_searchable_attribute` is the model attribute used to authenticate as the `username`. It defaults to `:subdomain`.
92
+
93
+ Ensure it is unique to the model records.
94
+
95
+ ###### Password
96
+ The config setting `basic_auth_model_authenticatable_attribute` is the model attribute used to authenticate as `password`. Defaults to `:api_token`.
97
+
98
+ Assuming the attribute is `:api_token`, generate the password using:
99
+ ```ruby
100
+ token = ScimRails::Encoder.encode(company)
101
+ # use the token as password for requests
102
+ company.api_token = token # required
103
+ company.save! # don't forget to persist the company record
104
+ ```
105
+
106
+ This is necessary irrespective of your authentication choice(s) - basic auth, oauth bearer or both.
107
+
108
+ ###### Sample Request
109
+
110
+ ```bash
111
+ $ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users'
112
+ ```
113
+
114
+ ##### OAuth Bearer
115
+
116
+ ###### Signing Algorithm
117
+ In the config settings, ensure you set `signing_algorithm` to a valid JWT signing algorithm, e.g "HS256". Defaults to `"none"` when not set.
118
+
119
+ ###### Signing Secret
120
+ In the config settings, ensure you set `signing_secret` to a secret key that will be used to encode and decode tokens. Defaults to `nil` when not set.
121
+
122
+ If you have already generated the `api_token` in the "Basic Auth" section, then use that as your bearer token and ignore the steps below:
123
+ ```ruby
124
+ token = ScimRails::Encoder.encode(company)
125
+ # use the token as bearer token for requests
126
+ company.api_token = token #required
127
+ company.save! # don't forget to persist the company record
128
+ ```
129
+
130
+ ##### Sample Request
131
+
132
+ ```bash
133
+ $ curl -H 'Authorization: Bearer xxxxxxx.xxxxxx' -X GET 'http://localhost:3000/scim/v2/Users'
134
+ ```
135
+
81
136
  ### List
82
137
 
83
138
  ##### All
@@ -195,6 +250,20 @@ Sample request:
195
250
  $ curl -X PATCH 'http://username:password@localhost:3000/scim/v2/Users/1' -d '{"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{"op": "replace", "value": { "active": false }}]}' -H 'Content-Type: application/scim+json'
196
251
  ```
197
252
 
253
+ ### Error Handling
254
+
255
+ By default, scim_rails will output any unhandled exceptions to your configured rails logs.
256
+
257
+ If you would like, you can supply a custom handler for exceptions in the initializer. The only requirement is that the value you supply responds to `#call`.
258
+
259
+ For example, you might want to notify Honeybadger:
260
+
261
+ ```ruby
262
+ ScimRails.configure do |config|
263
+ config.on_error = ->(e) { Honeybadger.notify(e) }
264
+ end
265
+ ```
266
+
198
267
  ## Contributing
199
268
 
200
269
  ### [Code of Conduct](https://github.com/lessonly/scim_rails/blob/master/CODE_OF_CONDUCT.md)
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
14
14
  rdoc.rdoc_files.include('lib/**/*.rb')
15
15
  end
16
16
 
17
- APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
18
  load 'rails/tasks/engine.rake'
19
19
 
20
20
 
@@ -12,12 +12,15 @@ module ScimRails
12
12
  end
13
13
 
14
14
  included do
15
- # StandardError must be ordered _first_ or it will catch all exceptions
16
- #
17
- # TODO: Build a plugin/configuration for error handling so that the
18
- # detailed production errors are logged somewhere if desired.
19
15
  if Rails.env.production?
20
- rescue_from StandardError do
16
+ rescue_from StandardError do |exception|
17
+ on_error = ScimRails.config.on_error
18
+ if on_error.respond_to?(:call)
19
+ on_error.call(exception)
20
+ else
21
+ Rails.logger.error(exception.inspect)
22
+ end
23
+
21
24
  json_response(
22
25
  {
23
26
  schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
@@ -9,14 +9,30 @@ module ScimRails
9
9
  private
10
10
 
11
11
  def authorize_request
12
- authenticate_with_http_basic do |username, password|
12
+ send(authentication_strategy) do |searchable_attribute, authentication_attribute|
13
13
  authorization = AuthorizeApiRequest.new(
14
- searchable_attribute: username,
15
- authentication_attribute: password
14
+ searchable_attribute: searchable_attribute,
15
+ authentication_attribute: authentication_attribute
16
16
  )
17
17
  @company = authorization.company
18
18
  end
19
- raise ScimRails::ExceptionHandler::InvalidCredentials if @company.blank?
19
+ raise ScimRails::ExceptionHandler::InvalidCredentials if @company.blank?
20
+ end
21
+
22
+ def authentication_strategy
23
+ if request.headers["Authorization"]&.include?("Bearer")
24
+ :authenticate_with_oauth_bearer
25
+ else
26
+ :authenticate_with_http_basic
27
+ end
28
+ end
29
+
30
+ def authenticate_with_oauth_bearer
31
+ authentication_attribute = request.headers["Authorization"].split(" ").last
32
+ payload = ScimRails::Encoder.decode(authentication_attribute).with_indifferent_access
33
+ searchable_attribute = payload[ScimRails.config.basic_auth_model_searchable_attribute]
34
+
35
+ yield searchable_attribute, authentication_attribute
20
36
  end
21
37
  end
22
38
  end
@@ -27,13 +27,17 @@ module ScimRails
27
27
  end
28
28
 
29
29
  def create
30
- username_key = ScimRails.config.queryable_user_attributes[:userName]
31
- find_by_username = Hash.new
32
- find_by_username[username_key] = permitted_user_params[username_key]
33
- user = @company
34
- .public_send(ScimRails.config.scim_users_scope)
35
- .find_or_create_by(find_by_username)
36
- user.update!(permitted_user_params)
30
+ if ScimRails.config.scim_user_prevent_update_on_create
31
+ user = @company.public_send(ScimRails.config.scim_users_scope).create!(permitted_user_params)
32
+ else
33
+ username_key = ScimRails.config.queryable_user_attributes[:userName]
34
+ find_by_username = Hash.new
35
+ find_by_username[username_key] = permitted_user_params[username_key]
36
+ user = @company
37
+ .public_send(ScimRails.config.scim_users_scope)
38
+ .find_or_create_by(find_by_username)
39
+ user.update!(permitted_user_params)
40
+ end
37
41
  update_status(user) unless put_active_param.nil?
38
42
  json_scim_response(object: user, status: :created)
39
43
  end
@@ -122,9 +126,23 @@ module ScimRails
122
126
  end
123
127
 
124
128
  def patch_active_param
125
- active = params.dig("Operations", 0, "value", "active")
126
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest if active.nil?
127
- active
129
+ handle_invalid = lambda do
130
+ raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
131
+ end
132
+
133
+ operations = params["Operations"] || {}
134
+
135
+ valid_operation = operations.find(handle_invalid) do |operation|
136
+ valid_patch_operation?(operation)
137
+ end
138
+
139
+ valid_operation.dig("value", "active")
140
+ end
141
+
142
+ def valid_patch_operation?(operation)
143
+ operation["op"].casecmp("replace") &&
144
+ operation["value"] &&
145
+ [true, false].include?(operation["value"]["active"])
128
146
  end
129
147
  end
130
148
  end
File without changes
@@ -14,10 +14,26 @@ ScimRails.configure do |config|
14
14
  # Model used for user records.
15
15
  config.scim_users_model = "User"
16
16
 
17
- # Metod used for retriving user records from the
17
+ # Method used for retrieving user records from the
18
18
  # authenticatable model.
19
19
  config.scim_users_scope = :users
20
20
 
21
+ # Determine whether the create endpoint updates users that already exist
22
+ # or throws an error (returning 409 Conflict in accordance with SCIM spec)
23
+ config.scim_user_prevent_update_on_create = false
24
+
25
+ # Cryptographic algorithm used for signing the auth tokens.
26
+ # It supports all algorithms supported by the jwt gem.
27
+ # See https://github.com/jwt/ruby-jwt#algorithms-and-usage for supported algorithms
28
+ # It is "none" by default, hence generated tokens are unsigned
29
+ # The tokens do not need to be signed if you only need basic authentication.
30
+ # config.signing_algorithm = "HS256"
31
+
32
+ # Secret token used to sign authorization tokens
33
+ # It is `nil` by default, hence generated tokens are unsigned
34
+ # The tokens do not need to be signed if you only need basic authentication.
35
+ # config.signing_secret = SECRET_TOKEN
36
+
21
37
  # Default sort order for pagination is by id. If you
22
38
  # use non sequential ids for user records, uncomment
23
39
  # the below line and configure a determinate order.
@@ -33,7 +49,7 @@ ScimRails.configure do |config|
33
49
  # Hash of queryable attribtues on the user model. If
34
50
  # the attribute is not listed in this hash it cannot
35
51
  # be queried by this Gem. The structure of this hash
36
- # is { queryable_scim_attribute => user_attribute }.
52
+ # is { queryable_scim_attribute => user_attribute }.
37
53
  config.queryable_user_attributes = {
38
54
  userName: :email,
39
55
  givenName: :first_name,
@@ -54,7 +70,7 @@ ScimRails.configure do |config|
54
70
  # for this Gem to figure out where to look in a SCIM
55
71
  # response for mutable values. This object should
56
72
  # include all attributes listed in
57
- # config.mutable_user_attributes.
73
+ # config.mutable_user_attributes.
58
74
  config.mutable_user_attributes_schema = {
59
75
  name: {
60
76
  givenName: :first_name,
data/lib/scim_rails.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "scim_rails/engine"
2
2
  require "scim_rails/config"
3
+ require "scim_rails/encoder"
3
4
 
4
5
  module ScimRails
5
6
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ScimRails
2
4
  class << self
3
5
  def configure
@@ -5,30 +7,40 @@ module ScimRails
5
7
  end
6
8
 
7
9
  def config
8
- @_config ||= Config.new
10
+ @config ||= Config.new
9
11
  end
10
12
  end
11
13
 
14
+ # Class containing configuration of ScimRails
12
15
  class Config
13
- attr_accessor \
16
+ ALGO_NONE = "none"
17
+
18
+ attr_writer \
14
19
  :basic_auth_model,
20
+ :mutable_user_attributes_schema,
21
+ :scim_users_model
22
+
23
+ attr_accessor \
15
24
  :basic_auth_model_authenticatable_attribute,
16
25
  :basic_auth_model_searchable_attribute,
17
26
  :mutable_user_attributes,
18
- :mutable_user_attributes_schema,
27
+ :on_error,
19
28
  :queryable_user_attributes,
20
29
  :scim_users_list_order,
21
- :scim_users_model,
22
30
  :scim_users_scope,
31
+ :scim_user_prevent_update_on_create,
32
+ :signing_secret,
33
+ :signing_algorithm,
23
34
  :user_attributes,
24
35
  :user_deprovision_method,
25
36
  :user_reprovision_method,
26
37
  :user_schema
27
-
38
+
28
39
  def initialize
29
40
  @basic_auth_model = "Company"
30
41
  @scim_users_list_order = :id
31
42
  @scim_users_model = "User"
43
+ @signing_algorithm = ALGO_NONE
32
44
  @user_schema = {}
33
45
  @user_attributes = []
34
46
  end
@@ -0,0 +1,25 @@
1
+ require "jwt"
2
+
3
+ module ScimRails
4
+ module Encoder
5
+ extend self
6
+
7
+ def encode(company)
8
+ payload = {
9
+ iat: Time.current.to_i,
10
+ ScimRails.config.basic_auth_model_searchable_attribute =>
11
+ company.public_send(ScimRails.config.basic_auth_model_searchable_attribute)
12
+ }
13
+
14
+ JWT.encode(payload, ScimRails.config.signing_secret, ScimRails.config.signing_algorithm)
15
+ end
16
+
17
+ def decode(token)
18
+ verify = ScimRails.config.signing_algorithm != ScimRails::Config::ALGO_NONE
19
+
20
+ JWT.decode(token, ScimRails.config.signing_secret, verify, algorithm: ScimRails.config.signing_algorithm).first
21
+ rescue JWT::VerificationError, JWT::DecodeError
22
+ raise ScimRails::ExceptionHandler::InvalidCredentials
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ScimRails
2
- VERSION = '0.2.0'
4
+ VERSION = "0.4.0"
3
5
  end
@@ -10,13 +10,13 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
10
10
 
11
11
  context "when unauthorized" do
12
12
  it "returns scim+json content type" do
13
- get :index
13
+ get :index, as: :json
14
14
 
15
- expect(response.content_type).to eq "application/scim+json"
15
+ expect(response.media_type).to eq "application/scim+json"
16
16
  end
17
17
 
18
18
  it "fails with no credentials" do
19
- get :index
19
+ get :index, as: :json
20
20
 
21
21
  expect(response.status).to eq 401
22
22
  end
@@ -24,7 +24,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
24
24
  it "fails with invalid credentials" do
25
25
  request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456")
26
26
 
27
- get :index
27
+ get :index, as: :json
28
28
 
29
29
  expect(response.status).to eq 401
30
30
  end
@@ -36,13 +36,13 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
36
36
  end
37
37
 
38
38
  it "returns scim+json content type" do
39
- get :index
39
+ get :index, as: :json
40
40
 
41
- expect(response.content_type).to eq "application/scim+json"
41
+ expect(response.media_type).to eq "application/scim+json"
42
42
  end
43
43
 
44
44
  it "is successful with valid credentials" do
45
- get :index
45
+ get :index, as: :json
46
46
 
47
47
  expect(response.status).to eq 200
48
48
  end
@@ -50,7 +50,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
50
50
  it "returns all results" do
51
51
  create_list(:user, 10, company: company)
52
52
 
53
- get :index
53
+ get :index, as: :json
54
54
  response_body = JSON.parse(response.body)
55
55
  expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:ListResponse"
56
56
  expect(response_body["totalResults"]).to eq 10
@@ -59,7 +59,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
59
59
  it "defaults to 100 results" do
60
60
  create_list(:user, 300, company: company)
61
61
 
62
- get :index
62
+ get :index, as: :json
63
63
  response_body = JSON.parse(response.body)
64
64
  expect(response_body["totalResults"]).to eq 300
65
65
  expect(response_body["Resources"].count).to eq 100
@@ -72,7 +72,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
72
72
  get :index, params: {
73
73
  startIndex: 101,
74
74
  count: 200,
75
- }
75
+ }, as: :json
76
76
  response_body = JSON.parse(response.body)
77
77
  expect(response_body["totalResults"]).to eq 400
78
78
  expect(response_body["Resources"].count).to eq 200
@@ -88,7 +88,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
88
88
  get :index, params: {
89
89
  startIndex: 1,
90
90
  count: 10,
91
- }
91
+ }, as: :json
92
92
  response_body = JSON.parse(response.body)
93
93
  expect(response_body["totalResults"]).to eq 400
94
94
  expect(response_body["Resources"].count).to eq 10
@@ -101,7 +101,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
101
101
 
102
102
  get :index, params: {
103
103
  filter: "email eq test1@example.com"
104
- }
104
+ }, as: :json
105
105
  response_body = JSON.parse(response.body)
106
106
  expect(response_body["totalResults"]).to eq 1
107
107
  expect(response_body["Resources"].count).to eq 1
@@ -113,7 +113,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
113
113
 
114
114
  get :index, params: {
115
115
  filter: "familyName eq Shellstrop"
116
- }
116
+ }, as: :json
117
117
  response_body = JSON.parse(response.body)
118
118
  expect(response_body["totalResults"]).to eq 1
119
119
  expect(response_body["Resources"].count).to eq 1
@@ -122,7 +122,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
122
122
  it "returns no results for unfound filter parameters" do
123
123
  get :index, params: {
124
124
  filter: "familyName eq fake_not_there"
125
- }
125
+ }, as: :json
126
126
  response_body = JSON.parse(response.body)
127
127
  expect(response_body["totalResults"]).to eq 0
128
128
  expect(response_body["Resources"].count).to eq 0
@@ -131,7 +131,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
131
131
  it "returns no results for undefined filter queries" do
132
132
  get :index, params: {
133
133
  filter: "address eq 101 Nowhere USA"
134
- }
134
+ }, as: :json
135
135
  expect(response.status).to eq 400
136
136
  response_body = JSON.parse(response.body)
137
137
  expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
@@ -145,13 +145,13 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
145
145
 
146
146
  context "when unauthorized" do
147
147
  it "returns scim+json content type" do
148
- get :show, params: { id: 1 }
148
+ get :show, params: { id: 1 }, as: :json
149
149
 
150
- expect(response.content_type).to eq "application/scim+json"
150
+ expect(response.media_type).to eq "application/scim+json"
151
151
  end
152
152
 
153
153
  it "fails with no credentials" do
154
- get :show, params: { id: 1 }
154
+ get :show, params: { id: 1 }, as: :json
155
155
 
156
156
  expect(response.status).to eq 401
157
157
  end
@@ -159,7 +159,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
159
159
  it "fails with invalid credentials" do
160
160
  request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456")
161
161
 
162
- get :show, params: { id: 1 }
162
+ get :show, params: { id: 1 }, as: :json
163
163
 
164
164
  expect(response.status).to eq 401
165
165
  end
@@ -171,20 +171,20 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
171
171
  end
172
172
 
173
173
  it "returns scim+json content type" do
174
- get :show, params: { id: 1 }
174
+ get :show, params: { id: 1 }, as: :json
175
175
 
176
- expect(response.content_type).to eq "application/scim+json"
176
+ expect(response.media_type).to eq "application/scim+json"
177
177
  end
178
178
 
179
179
  it "is successful with valid credentials" do
180
180
  create(:user, id: 1, company: company)
181
- get :show, params: { id: 1 }
181
+ get :show, params: { id: 1 }, as: :json
182
182
 
183
183
  expect(response.status).to eq 200
184
184
  end
185
185
 
186
186
  it "returns :not_found for id that cannot be found" do
187
- get :show, params: { id: "fake_id" }
187
+ get :show, params: { id: "fake_id" }, as: :json
188
188
 
189
189
  expect(response.status).to eq 404
190
190
  end
@@ -193,7 +193,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
193
193
  new_company = create(:company)
194
194
  create(:user, company: new_company, id: 1)
195
195
 
196
- get :show, params: { id: 1 }
196
+ get :show, params: { id: 1 }, as: :json
197
197
 
198
198
  expect(response.status).to eq 404
199
199
  end
@@ -206,13 +206,13 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
206
206
 
207
207
  context "when unauthorized" do
208
208
  it "returns scim+json content type" do
209
- post :create
209
+ post :create, as: :json
210
210
 
211
- expect(response.content_type).to eq "application/scim+json"
211
+ expect(response.media_type).to eq "application/scim+json"
212
212
  end
213
213
 
214
214
  it "fails with no credentials" do
215
- post :create
215
+ post :create, as: :json
216
216
 
217
217
  expect(response.status).to eq 401
218
218
  end
@@ -220,7 +220,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
220
220
  it "fails with invalid credentials" do
221
221
  request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456")
222
222
 
223
- post :create
223
+ post :create, as: :json
224
224
 
225
225
  expect(response.status).to eq 401
226
226
  end
@@ -242,9 +242,9 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
242
242
  value: "new@example.com"
243
243
  }
244
244
  ]
245
- }
245
+ }, as: :json
246
246
 
247
- expect(response.content_type).to eq "application/scim+json"
247
+ expect(response.media_type).to eq "application/scim+json"
248
248
  end
249
249
 
250
250
  it "is successful with valid credentials" do
@@ -260,7 +260,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
260
260
  value: "new@example.com"
261
261
  }
262
262
  ]
263
- }
263
+ }, as: :json
264
264
 
265
265
  expect(response.status).to eq 201
266
266
  expect(company.users.count).to eq 1
@@ -283,7 +283,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
283
283
  value: "new@example.com"
284
284
  }
285
285
  ]
286
- }
286
+ }, as: :json
287
287
 
288
288
  expect(response.status).to eq 201
289
289
  expect(company.users.count).to eq 1
@@ -299,7 +299,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
299
299
  value: "new@example.com"
300
300
  }
301
301
  ]
302
- }
302
+ }, as: :json
303
303
 
304
304
  expect(response.status).to eq 422
305
305
  expect(company.users.count).to eq 0
@@ -318,13 +318,33 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
318
318
  value: "new@example.com"
319
319
  }
320
320
  ]
321
- }
321
+ }, as: :json
322
322
 
323
323
  expect(response.status).to eq 201
324
324
  expect(company.users.count).to eq 1
325
325
  expect(company.users.first.first_name).to eq "Not New"
326
326
  end
327
327
 
328
+ it "returns 409 if user already exists and config.scim_user_prevent_update_on_create is set to true" do
329
+ allow(ScimRails.config).to receive(:scim_user_prevent_update_on_create).and_return(true)
330
+ create(:user, email: "new@example.com", company: company)
331
+
332
+ post :create, params: {
333
+ name: {
334
+ givenName: "Not New",
335
+ familyName: "User"
336
+ },
337
+ emails: [
338
+ {
339
+ value: "new@example.com"
340
+ }
341
+ ]
342
+ }, as: :json
343
+
344
+ expect(response.status).to eq 409
345
+ expect(company.users.count).to eq 1
346
+ end
347
+
328
348
  it "creates and archives inactive user" do
329
349
  post :create, params: {
330
350
  id: 1,
@@ -339,7 +359,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
339
359
  },
340
360
  ],
341
361
  active: "false"
342
- }
362
+ }, as: :json
343
363
 
344
364
  expect(response.status).to eq 201
345
365
  expect(company.users.count).to eq 1
@@ -355,13 +375,13 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
355
375
 
356
376
  context "when unauthorized" do
357
377
  it "returns scim+json content type" do
358
- put :put_update, params: { id: 1 }
378
+ put :put_update, params: { id: 1 }, as: :json
359
379
 
360
- expect(response.content_type).to eq "application/scim+json"
380
+ expect(response.media_type).to eq "application/scim+json"
361
381
  end
362
382
 
363
383
  it "fails with no credentials" do
364
- put :put_update, params: { id: 1 }
384
+ put :put_update, params: { id: 1 }, as: :json
365
385
 
366
386
  expect(response.status).to eq 401
367
387
  end
@@ -369,7 +389,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
369
389
  it "fails with invalid credentials" do
370
390
  request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456")
371
391
 
372
- put :put_update, params: { id: 1 }
392
+ put :put_update, params: { id: 1 }, as: :json
373
393
 
374
394
  expect(response.status).to eq 401
375
395
  end
@@ -383,20 +403,20 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
383
403
  end
384
404
 
385
405
  it "returns scim+json content type" do
386
- put :put_update, params: put_params
406
+ put :put_update, params: put_params, as: :json
387
407
 
388
- expect(response.content_type).to eq "application/scim+json"
408
+ expect(response.media_type).to eq "application/scim+json"
389
409
  end
390
410
 
391
411
  it "is successful with with valid credentials" do
392
- put :put_update, params: put_params
412
+ put :put_update, params: put_params, as: :json
393
413
 
394
414
  expect(response.status).to eq 200
395
415
  end
396
416
 
397
417
  it "deprovisions an active record" do
398
418
  request.content_type = "application/scim+json"
399
- put :put_update, params: put_params(active: false)
419
+ put :put_update, params: put_params(active: false), as: :json
400
420
 
401
421
  expect(response.status).to eq 200
402
422
  expect(user.reload.active?).to eq false
@@ -406,14 +426,14 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
406
426
  user.archive!
407
427
  expect(user.reload.active?).to eq false
408
428
  request.content_type = "application/scim+json"
409
- put :put_update, params: put_params(active: true)
429
+ put :put_update, params: put_params(active: true), as: :json
410
430
 
411
431
  expect(response.status).to eq 200
412
432
  expect(user.reload.active?).to eq true
413
433
  end
414
434
 
415
435
  it "returns :not_found for id that cannot be found" do
416
- get :put_update, params: { id: "fake_id" }
436
+ get :put_update, params: { id: "fake_id" }, as: :json
417
437
 
418
438
  expect(response.status).to eq 404
419
439
  end
@@ -422,7 +442,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
422
442
  new_company = create(:company)
423
443
  create(:user, company: new_company, id: 1000)
424
444
 
425
- get :put_update, params: { id: 1000 }
445
+ get :put_update, params: { id: 1000 }, as: :json
426
446
 
427
447
  expect(response.status).to eq 404
428
448
  end
@@ -437,7 +457,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
437
457
  },
438
458
  ],
439
459
  active: "true"
440
- }
460
+ }, as: :json
441
461
 
442
462
  expect(response.status).to eq 422
443
463
  end
@@ -450,13 +470,13 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
450
470
 
451
471
  context "when unauthorized" do
452
472
  it "returns scim+json content type" do
453
- patch :patch_update, params: patch_params(id: 1)
473
+ patch :patch_update, params: patch_params(id: 1), as: :json
454
474
 
455
- expect(response.content_type).to eq "application/scim+json"
475
+ expect(response.media_type).to eq "application/scim+json"
456
476
  end
457
477
 
458
478
  it "fails with no credentials" do
459
- patch :patch_update, params: patch_params(id: 1)
479
+ patch :patch_update, params: patch_params(id: 1), as: :json
460
480
 
461
481
  expect(response.status).to eq 401
462
482
  end
@@ -464,7 +484,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
464
484
  it "fails with invalid credentials" do
465
485
  request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456")
466
486
 
467
- patch :patch_update, params: patch_params(id: 1)
487
+ patch :patch_update, params: patch_params(id: 1), as: :json
468
488
 
469
489
  expect(response.status).to eq 401
470
490
  end
@@ -478,19 +498,19 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
478
498
  end
479
499
 
480
500
  it "returns scim+json content type" do
481
- patch :patch_update, params: patch_params(id: 1)
501
+ patch :patch_update, params: patch_params(id: 1), as: :json
482
502
 
483
- expect(response.content_type).to eq "application/scim+json"
503
+ expect(response.media_type).to eq "application/scim+json"
484
504
  end
485
505
 
486
506
  it "is successful with valid credentials" do
487
- patch :patch_update, params: patch_params(id: 1)
507
+ patch :patch_update, params: patch_params(id: 1), as: :json
488
508
 
489
509
  expect(response.status).to eq 200
490
510
  end
491
511
 
492
512
  it "returns :not_found for id that cannot be found" do
493
- get :patch_update, params: patch_params(id: "fake_id")
513
+ get :patch_update, params: patch_params(id: "fake_id"), as: :json
494
514
 
495
515
  expect(response.status).to eq 404
496
516
  end
@@ -499,7 +519,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
499
519
  new_company = create(:company)
500
520
  create(:user, company: new_company, id: 1000)
501
521
 
502
- get :patch_update, params: patch_params(id: 1000)
522
+ get :patch_update, params: patch_params(id: 1000), as: :json
503
523
 
504
524
  expect(response.status).to eq 404
505
525
  end
@@ -509,7 +529,7 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
509
529
  user = company.users.first
510
530
  expect(user.archived?).to eq false
511
531
 
512
- patch :patch_update, params: patch_params(id: 1)
532
+ patch :patch_update, params: patch_params(id: 1), as: :json
513
533
 
514
534
  expect(response.status).to eq 200
515
535
  expect(company.users.count).to eq 1
@@ -517,12 +537,12 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
517
537
  expect(user.archived?).to eq true
518
538
  end
519
539
 
520
- it "sucessfully restores user" do
540
+ it "successfully restores user" do
521
541
  expect(company.users.count).to eq 1
522
542
  user = company.users.first.tap(&:archive!)
523
543
  expect(user.archived?).to eq true
524
544
 
525
- patch :patch_update, params: patch_params(id: 1, active: true)
545
+ patch :patch_update, params: patch_params(id: 1, active: true), as: :json
526
546
 
527
547
  expect(response.status).to eq 200
528
548
  expect(company.users.count).to eq 1
@@ -530,6 +550,24 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
530
550
  expect(user.archived?).to eq false
531
551
  end
532
552
 
553
+ it "is case insensetive for op value" do
554
+ # Note, this is for backward compatibility. op should always
555
+ # be lower case and support for case insensitivity will be removed
556
+ patch :patch_update, params: {
557
+ id: 1,
558
+ Operations: [
559
+ {
560
+ op: "Replace",
561
+ value: {
562
+ active: false
563
+ }
564
+ }
565
+ ]
566
+ }, as: :json
567
+
568
+ expect(response.status).to eq 200
569
+ end
570
+
533
571
  it "throws an error for non status updates" do
534
572
  patch :patch_update, params: {
535
573
  id: 1,
@@ -543,12 +581,59 @@ RSpec.describe ScimRails::ScimUsersController, type: :controller do
543
581
  }
544
582
  }
545
583
  ]
584
+ }, as: :json
585
+
586
+ expect(response.status).to eq 422
587
+ response_body = JSON.parse(response.body)
588
+ expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
589
+ end
590
+
591
+ it "returns 422 when value is not an object" do
592
+ patch :patch_update, params: {
593
+ id: 1,
594
+ Operations: [
595
+ {
596
+ op: "replace",
597
+ path: "displayName",
598
+ value: "Francis"
599
+ }
600
+ ]
546
601
  }
547
602
 
548
603
  expect(response.status).to eq 422
549
604
  response_body = JSON.parse(response.body)
550
605
  expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
551
606
  end
607
+
608
+ it "returns 422 when value is missing" do
609
+ patch :patch_update, params: {
610
+ id: 1,
611
+ Operations: [
612
+ {
613
+ op: "replace"
614
+ }
615
+ ]
616
+ }, as: :json
617
+
618
+ expect(response.status).to eq 422
619
+ response_body = JSON.parse(response.body)
620
+ expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
621
+ end
622
+
623
+ it "returns 422 operations key is missing" do
624
+ patch :patch_update, params: {
625
+ id: 1,
626
+ Foobars: [
627
+ {
628
+ op: "replace"
629
+ }
630
+ ]
631
+ }, as: :json
632
+
633
+ expect(response.status).to eq 422
634
+ response_body = JSON.parse(response.body)
635
+ expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error"
636
+ end
552
637
  end
553
638
  end
554
639