scim_rails 0.1.4 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +69 -2
- data/Rakefile +1 -1
- data/app/controllers/concerns/scim_rails/exception_handler.rb +8 -5
- data/app/controllers/concerns/scim_rails/response.rb +1 -1
- data/app/controllers/scim_rails/application_controller.rb +20 -4
- data/app/controllers/scim_rails/scim_users_controller.rb +28 -10
- data/{spec/dummy/tmp/restart.txt → config/environment.rb} +0 -0
- data/lib/generators/scim_rails/templates/initializer.rb +19 -3
- data/lib/scim_rails.rb +1 -0
- data/lib/scim_rails/config.rb +17 -5
- data/lib/scim_rails/encoder.rb +25 -0
- data/lib/scim_rails/version.rb +3 -1
- data/spec/controllers/scim_rails/scim_users_controller_spec.rb +144 -59
- data/spec/controllers/scim_rails/scim_users_request_spec.rb +41 -19
- data/spec/dummy/config/initializers/new_framework_defaults.rb +2 -2
- data/spec/dummy/config/initializers/scim_rails_config.rb +3 -0
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/K3/K3kRdEIVqv2uHDkXatQjmCumpOCKxtnexZuiH4Ad37A.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/TH/THOPrXYljWHCQGbFjofWsaZNw7w-hfqfI3-hnxWyGas.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/TW/TW6bO0RoLe0_sbLapNXYMgZbgPTnnSFdrhh6pv917KA.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/kW/kWKxWPvgf53JDnfwnEOVTJYtSWS971rKq3BhmUzYaXY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/mX/mX1nlsL_SWOB4y22W5FheRX0YEONKyOY7xUeIvRiHco.cache +2 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/pR/pRHZ5T7C7u6vDXHn5oM357U3KshRFgRMbA53zz4Azcw.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v3.0/q8/q8BHffCjwsZ85QWxK1lyx5t_0jQSLlTtLGhRrwuWXGI.cache +0 -0
- data/spec/factories/company.rb +4 -1
- data/spec/lib/scim_rails/encoder_spec.rb +62 -0
- data/spec/support/scim_rails_config.rb +3 -0
- metadata +73 -22
- data/spec/dummy/log/development.log +0 -1531
- data/spec/dummy/log/test.log +0 -107108
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a512e0428de106c5cca2fcd17935cdbf27ac9b1cb9e1c3300e97fd938c64e834
|
4
|
+
data.tar.gz: fdb2be8912304c726a4238d4b890c231d9224133cb793014bcb0bb90df41037a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b1e54f6ccfc8709849313936376fbc07964afc2b45faf8c39d901857cc66a4577c77904f425bd2ed3d5811fd642621f5e5e9197e13d48da2a5a83a61cf52332
|
7
|
+
data.tar.gz: 74a09e8f9383023ec5b3f0d5d328ba3bd385faac1b4878efdae1c6ddd0fc184eaa053eac567e491e2991caae576c19a102352efd266f04be3efbcf6158b235ce
|
data/README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
[![Build Status](https://travis-ci.com/lessonly/scim_rails.svg?branch=master)](https://travis-ci.com/lessonly/scim_rails)
|
2
|
+
|
1
3
|
# ScimRails
|
2
4
|
|
3
5
|
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.
|
4
6
|
|
5
7
|
#### What is SCIM?
|
6
8
|
|
7
|
-
SCIM stands for System for Cross-domain Identity Management. At its core, it is a set of rules defining how apps should interact for the purpose of creating, updating, and deprovisioning users. SCIM requests and responses can be sent in XML or JSON and this Gem uses JSON for ease of readability.
|
9
|
+
SCIM stands for System for Cross-domain Identity Management. At its core, it is a set of rules defining how apps should interact for the purpose of creating, updating, and deprovisioning users. SCIM requests and responses can be sent in XML or JSON and this Gem uses JSON for ease of readability.
|
8
10
|
|
9
11
|
To learn more about SCIM 2.0 you can read the documentation at [RFC 7643](https://tools.ietf.org/html/rfc7643) and [RFC 7644](https://tools.ietf.org/html/rfc7644).
|
10
12
|
|
@@ -76,7 +78,58 @@ https://example.com/example/scim/v2/Users
|
|
76
78
|
|
77
79
|
When sending requests to the server the `Content-Type` should be set to `application/scim+json` but will also respond to `application/json`.
|
78
80
|
|
79
|
-
All responses will be sent with a `Content-Type` of `application/scim+json
|
81
|
+
All responses will be sent with a `Content-Type` of `application/scim+json`.
|
82
|
+
|
83
|
+
#### Authentication
|
84
|
+
|
85
|
+
This gem supports both basic and OAuth bearer authentication.
|
86
|
+
|
87
|
+
##### Basic Auth
|
88
|
+
###### Username
|
89
|
+
The config setting `basic_auth_model_searchable_attribute` is the model attribute used to authenticate as the `username`. It defaults to `:subdomain`.
|
90
|
+
|
91
|
+
Ensure it is unique to the model records.
|
92
|
+
|
93
|
+
###### Password
|
94
|
+
The config setting `basic_auth_model_authenticatable_attribute` is the model attribute used to authenticate as `password`. Defaults to `:api_token`.
|
95
|
+
|
96
|
+
Assuming the attribute is `:api_token`, generate the password using:
|
97
|
+
```ruby
|
98
|
+
token = ScimRails::Encoder.encode(company)
|
99
|
+
# use the token as password for requests
|
100
|
+
company.api_token = token # required
|
101
|
+
company.save! # don't forget to persist the company record
|
102
|
+
```
|
103
|
+
|
104
|
+
This is necessary irrespective of your authentication choice(s) - basic auth, oauth bearer or both.
|
105
|
+
|
106
|
+
###### Sample Request
|
107
|
+
|
108
|
+
```bash
|
109
|
+
$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users'
|
110
|
+
```
|
111
|
+
|
112
|
+
##### OAuth Bearer
|
113
|
+
|
114
|
+
###### Signing Algorithm
|
115
|
+
In the config settings, ensure you set `signing_algorithm` to a valid JWT signing algorithm, e.g "HS256". Defaults to `"none"` when not set.
|
116
|
+
|
117
|
+
###### Signing Secret
|
118
|
+
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.
|
119
|
+
|
120
|
+
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:
|
121
|
+
```ruby
|
122
|
+
token = ScimRails::Encoder.encode(company)
|
123
|
+
# use the token as bearer token for requests
|
124
|
+
company.api_token = token #required
|
125
|
+
company.save! # don't forget to persist the company record
|
126
|
+
```
|
127
|
+
|
128
|
+
##### Sample Request
|
129
|
+
|
130
|
+
```bash
|
131
|
+
$ curl -H 'Authorization: Bearer xxxxxxx.xxxxxx' -X GET 'http://localhost:3000/scim/v2/Users'
|
132
|
+
```
|
80
133
|
|
81
134
|
### List
|
82
135
|
|
@@ -195,6 +248,20 @@ Sample request:
|
|
195
248
|
$ 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
249
|
```
|
197
250
|
|
251
|
+
### Error Handling
|
252
|
+
|
253
|
+
By default, scim_rails will output any unhandled exceptions to your configured rails logs.
|
254
|
+
|
255
|
+
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`.
|
256
|
+
|
257
|
+
For example, you might want to notify Honeybadger:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
ScimRails.configure do |config|
|
261
|
+
config.on_error = ->(e) { Honeybadger.notify(e) }
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
198
265
|
## Contributing
|
199
266
|
|
200
267
|
### [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("../
|
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
|
-
|
12
|
+
send(authentication_strategy) do |searchable_attribute, authentication_attribute|
|
13
13
|
authorization = AuthorizeApiRequest.new(
|
14
|
-
searchable_attribute:
|
15
|
-
authentication_attribute:
|
14
|
+
searchable_attribute: searchable_attribute,
|
15
|
+
authentication_attribute: authentication_attribute
|
16
16
|
)
|
17
17
|
@company = authorization.company
|
18
18
|
end
|
19
|
-
raise
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
.
|
35
|
-
|
36
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
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
|
-
#
|
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
data/lib/scim_rails/config.rb
CHANGED
@@ -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
|
-
@
|
10
|
+
@config ||= Config.new
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
14
|
+
# Class containing configuration of ScimRails
|
12
15
|
class Config
|
13
|
-
|
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
|
-
:
|
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
|
data/lib/scim_rails/version.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 "
|
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
|
|