scimaenaga 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +314 -0
- data/Rakefile +34 -0
- data/app/controllers/concerns/scim_rails/exception_handler.rb +119 -0
- data/app/controllers/concerns/scim_rails/response.rb +94 -0
- data/app/controllers/scim_rails/application_controller.rb +72 -0
- data/app/controllers/scim_rails/scim_groups_controller.rb +96 -0
- data/app/controllers/scim_rails/scim_users_controller.rb +124 -0
- data/app/helpers/scim_rails/application_helper.rb +4 -0
- data/app/models/scim_rails/application_record.rb +5 -0
- data/app/models/scim_rails/authorize_api_request.rb +40 -0
- data/app/models/scim_rails/scim_count.rb +38 -0
- data/app/models/scim_rails/scim_query_parser.rb +47 -0
- data/config/initializers/mime_types.rb +5 -0
- data/config/routes.rb +12 -0
- data/lib/generators/scim_rails/USAGE +8 -0
- data/lib/generators/scim_rails/scim_rails_generator.rb +7 -0
- data/lib/generators/scim_rails/templates/initializer.rb +166 -0
- data/lib/scim_rails/config.rb +85 -0
- data/lib/scim_rails/encoder.rb +25 -0
- data/lib/scim_rails/engine.rb +12 -0
- data/lib/scim_rails/version.rb +5 -0
- data/lib/scim_rails.rb +6 -0
- data/lib/tasks/scim_rails_tasks.rake +4 -0
- data/spec/controllers/scim_rails/scim_groups_controller_spec.rb +494 -0
- data/spec/controllers/scim_rails/scim_groups_request_spec.rb +68 -0
- data/spec/controllers/scim_rails/scim_users_controller_spec.rb +681 -0
- data/spec/controllers/scim_rails/scim_users_request_spec.rb +77 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +5 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/jobs/application_job.rb +2 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/company.rb +4 -0
- data/spec/dummy/app/models/group.rb +15 -0
- data/spec/dummy/app/models/group_user.rb +6 -0
- data/spec/dummy/app/models/user.rb +39 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +34 -0
- data/spec/dummy/bin/update +29 -0
- data/spec/dummy/config/application.rb +15 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +9 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +54 -0
- data/spec/dummy/config/environments/production.rb +86 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/new_framework_defaults.rb +24 -0
- data/spec/dummy/config/initializers/scim_rails_config.rb +85 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/puma.rb +47 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/db/migrate/20181206184304_create_users.rb +15 -0
- data/spec/dummy/db/migrate/20181206184313_create_companies.rb +11 -0
- data/spec/dummy/db/migrate/20210423075859_create_groups.rb +10 -0
- data/spec/dummy/db/migrate/20210423075950_create_group_users.rb +10 -0
- data/spec/dummy/db/schema.rb +53 -0
- data/spec/dummy/db/seeds.rb +14 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/company.rb +10 -0
- data/spec/factories/group.rb +11 -0
- data/spec/factories/user.rb +9 -0
- data/spec/lib/scim_rails/encoder_spec.rb +62 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/auth_helper.rb +7 -0
- data/spec/support/factory_bot.rb +3 -0
- data/spec/support/scim_rails_config.rb +59 -0
- metadata +339 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScimRails
|
4
|
+
class ScimGroupsController < ScimRails::ApplicationController
|
5
|
+
def index
|
6
|
+
if params[:filter].present?
|
7
|
+
query = ScimRails::ScimQueryParser.new(
|
8
|
+
params[:filter], ScimRails.config.queryable_group_attributes
|
9
|
+
)
|
10
|
+
|
11
|
+
groups = @company
|
12
|
+
.public_send(ScimRails.config.scim_groups_scope)
|
13
|
+
.where(
|
14
|
+
"#{ScimRails.config.scim_groups_model.connection.quote_column_name(query.attribute)} #{query.operator} ?",
|
15
|
+
query.parameter
|
16
|
+
)
|
17
|
+
.order(ScimRails.config.scim_groups_list_order)
|
18
|
+
else
|
19
|
+
groups = @company
|
20
|
+
.public_send(ScimRails.config.scim_groups_scope)
|
21
|
+
.preload(:users)
|
22
|
+
.order(ScimRails.config.scim_groups_list_order)
|
23
|
+
end
|
24
|
+
|
25
|
+
counts = ScimCount.new(
|
26
|
+
start_index: params[:startIndex],
|
27
|
+
limit: params[:count],
|
28
|
+
total: groups.count
|
29
|
+
)
|
30
|
+
|
31
|
+
json_scim_response(object: groups, counts: counts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def show
|
35
|
+
group = @company
|
36
|
+
.public_send(ScimRails.config.scim_groups_scope)
|
37
|
+
.find(params[:id])
|
38
|
+
json_scim_response(object: group)
|
39
|
+
end
|
40
|
+
|
41
|
+
def create
|
42
|
+
group = @company
|
43
|
+
.public_send(ScimRails.config.scim_groups_scope)
|
44
|
+
.create!(permitted_group_params)
|
45
|
+
|
46
|
+
json_scim_response(object: group, status: :created)
|
47
|
+
end
|
48
|
+
|
49
|
+
def put_update
|
50
|
+
group = @company
|
51
|
+
.public_send(ScimRails.config.scim_groups_scope)
|
52
|
+
.find(params[:id])
|
53
|
+
group.update!(permitted_group_params)
|
54
|
+
json_scim_response(object: group)
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy
|
58
|
+
unless ScimRails.config.group_destroy_method
|
59
|
+
raise ScimRails::ExceptionHandler::UnsupportedDeleteRequest
|
60
|
+
end
|
61
|
+
group = @company
|
62
|
+
.public_send(ScimRails.config.scim_groups_scope)
|
63
|
+
.find(params[:id])
|
64
|
+
group.public_send(ScimRails.config.group_destroy_method)
|
65
|
+
head :no_content
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def permitted_group_params
|
71
|
+
converted = mutable_attributes.each.with_object({}) do |attribute, hash|
|
72
|
+
hash[attribute] = find_value_for(attribute)
|
73
|
+
end
|
74
|
+
return converted unless params[:members]
|
75
|
+
|
76
|
+
converted.merge(member_params)
|
77
|
+
end
|
78
|
+
|
79
|
+
def member_params
|
80
|
+
{
|
81
|
+
ScimRails.config.group_member_relation_attribute =>
|
82
|
+
params[:members].map do |member|
|
83
|
+
member[ScimRails.config.group_member_relation_schema.keys.first]
|
84
|
+
end
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def mutable_attributes
|
89
|
+
ScimRails.config.mutable_group_attributes
|
90
|
+
end
|
91
|
+
|
92
|
+
def controller_schema
|
93
|
+
ScimRails.config.mutable_group_attributes_schema
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScimRails
|
4
|
+
class ScimUsersController < ScimRails::ApplicationController
|
5
|
+
def index
|
6
|
+
if params[:filter].present?
|
7
|
+
query = ScimRails::ScimQueryParser.new(
|
8
|
+
params[:filter], ScimRails.config.queryable_user_attributes
|
9
|
+
)
|
10
|
+
|
11
|
+
users = @company
|
12
|
+
.public_send(ScimRails.config.scim_users_scope)
|
13
|
+
.where(
|
14
|
+
"#{ScimRails.config.scim_users_model.connection.quote_column_name(query.attribute)} #{query.operator} ?",
|
15
|
+
query.parameter
|
16
|
+
)
|
17
|
+
.order(ScimRails.config.scim_users_list_order)
|
18
|
+
else
|
19
|
+
users = @company
|
20
|
+
.public_send(ScimRails.config.scim_users_scope)
|
21
|
+
.order(ScimRails.config.scim_users_list_order)
|
22
|
+
end
|
23
|
+
|
24
|
+
counts = ScimCount.new(
|
25
|
+
start_index: params[:startIndex],
|
26
|
+
limit: params[:count],
|
27
|
+
total: users.count
|
28
|
+
)
|
29
|
+
|
30
|
+
json_scim_response(object: users, counts: counts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create
|
34
|
+
if ScimRails.config.scim_user_prevent_update_on_create
|
35
|
+
user = @company.public_send(ScimRails.config.scim_users_scope).create!(permitted_user_params)
|
36
|
+
else
|
37
|
+
username_key = ScimRails.config.queryable_user_attributes[:userName]
|
38
|
+
find_by_username = {}
|
39
|
+
find_by_username[username_key] = permitted_user_params[username_key]
|
40
|
+
user = @company
|
41
|
+
.public_send(ScimRails.config.scim_users_scope)
|
42
|
+
.find_or_create_by(find_by_username)
|
43
|
+
user.update!(permitted_user_params)
|
44
|
+
end
|
45
|
+
update_status(user) unless put_active_param.nil?
|
46
|
+
json_scim_response(object: user, status: :created)
|
47
|
+
end
|
48
|
+
|
49
|
+
def show
|
50
|
+
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
51
|
+
json_scim_response(object: user)
|
52
|
+
end
|
53
|
+
|
54
|
+
def put_update
|
55
|
+
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
56
|
+
update_status(user) unless put_active_param.nil?
|
57
|
+
user.update!(permitted_user_params)
|
58
|
+
json_scim_response(object: user)
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: PATCH will only deprovision or reprovision users.
|
62
|
+
# This will work just fine for Okta but is not SCIM compliant.
|
63
|
+
def patch_update
|
64
|
+
user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
|
65
|
+
update_status(user)
|
66
|
+
json_scim_response(object: user)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def permitted_user_params
|
72
|
+
ScimRails.config.mutable_user_attributes.each.with_object({}) do |attribute, hash|
|
73
|
+
hash[attribute] = find_value_for(attribute)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def controller_schema
|
78
|
+
ScimRails.config.mutable_user_attributes_schema
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_status(user)
|
82
|
+
user.public_send(ScimRails.config.user_reprovision_method) if active?
|
83
|
+
user.public_send(ScimRails.config.user_deprovision_method) unless active?
|
84
|
+
end
|
85
|
+
|
86
|
+
def active?
|
87
|
+
active = put_active_param
|
88
|
+
active = patch_active_param if active.nil?
|
89
|
+
|
90
|
+
case active
|
91
|
+
when true, "true", 1
|
92
|
+
true
|
93
|
+
when false, "false", 0
|
94
|
+
false
|
95
|
+
else
|
96
|
+
raise ActiveRecord::RecordInvalid
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def put_active_param
|
101
|
+
params[:active]
|
102
|
+
end
|
103
|
+
|
104
|
+
def patch_active_param
|
105
|
+
handle_invalid = lambda do
|
106
|
+
raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
|
107
|
+
end
|
108
|
+
|
109
|
+
operations = params["Operations"] || {}
|
110
|
+
|
111
|
+
valid_operation = operations.find(handle_invalid) do |operation|
|
112
|
+
valid_patch_operation?(operation)
|
113
|
+
end
|
114
|
+
|
115
|
+
valid_operation.dig("value", "active")
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid_patch_operation?(operation)
|
119
|
+
operation["op"].casecmp("replace") &&
|
120
|
+
operation["value"] &&
|
121
|
+
[true, false].include?(operation["value"]["active"])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ScimRails
|
2
|
+
class AuthorizeApiRequest
|
3
|
+
|
4
|
+
def initialize(searchable_attribute:, authentication_attribute:)
|
5
|
+
@searchable_attribute = searchable_attribute
|
6
|
+
@authentication_attribute = authentication_attribute
|
7
|
+
|
8
|
+
raise ScimRails::ExceptionHandler::InvalidCredentials if searchable_attribute.blank? || authentication_attribute.blank?
|
9
|
+
|
10
|
+
@search_parameter = { ScimRails.config.basic_auth_model_searchable_attribute => @searchable_attribute }
|
11
|
+
end
|
12
|
+
|
13
|
+
def company
|
14
|
+
company = find_company
|
15
|
+
authorize(company)
|
16
|
+
company
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :authentication_attribute
|
22
|
+
attr_reader :search_parameter
|
23
|
+
attr_reader :searchable_attribute
|
24
|
+
|
25
|
+
def find_company
|
26
|
+
@company ||= ScimRails.config.basic_auth_model.find_by!(search_parameter)
|
27
|
+
|
28
|
+
rescue ActiveRecord::RecordNotFound
|
29
|
+
raise ScimRails::ExceptionHandler::InvalidCredentials
|
30
|
+
end
|
31
|
+
|
32
|
+
def authorize(authentication_model)
|
33
|
+
authorized = ActiveSupport::SecurityUtils.secure_compare(
|
34
|
+
authentication_model.public_send(ScimRails.config.basic_auth_model_authenticatable_attribute),
|
35
|
+
authentication_attribute
|
36
|
+
)
|
37
|
+
raise ScimRails::ExceptionHandler::InvalidCredentials unless authorized
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ScimRails
|
2
|
+
class ScimCount
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor \
|
6
|
+
:limit,
|
7
|
+
:offset,
|
8
|
+
:start_index,
|
9
|
+
:total
|
10
|
+
|
11
|
+
def limit
|
12
|
+
return 100 if @limit.blank?
|
13
|
+
validate_numericality(@limit)
|
14
|
+
input = @limit.to_i
|
15
|
+
raise if input < 1
|
16
|
+
input
|
17
|
+
end
|
18
|
+
|
19
|
+
def start_index
|
20
|
+
return 1 if @start_index.blank?
|
21
|
+
validate_numericality(@start_index)
|
22
|
+
input = @start_index.to_i
|
23
|
+
return 1 if input < 1
|
24
|
+
input
|
25
|
+
end
|
26
|
+
|
27
|
+
def offset
|
28
|
+
start_index - 1
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_numericality(input)
|
34
|
+
raise unless input.match?(/\A\d+\z/)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScimRails
|
4
|
+
class ScimQueryParser
|
5
|
+
attr_accessor :query_elements, :query_attributes
|
6
|
+
|
7
|
+
def initialize(query_string, queryable_attributes)
|
8
|
+
self.query_elements = query_string.split
|
9
|
+
self.query_attributes = queryable_attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def attribute
|
13
|
+
attribute = query_elements[0]
|
14
|
+
raise ScimRails::ExceptionHandler::InvalidQuery if attribute.blank?
|
15
|
+
|
16
|
+
attribute = attribute.to_sym
|
17
|
+
|
18
|
+
mapped_attribute = query_attributes[attribute]
|
19
|
+
raise ScimRails::ExceptionHandler::InvalidQuery if mapped_attribute.blank?
|
20
|
+
|
21
|
+
mapped_attribute
|
22
|
+
end
|
23
|
+
|
24
|
+
def operator
|
25
|
+
sql_comparison_operator(query_elements[1])
|
26
|
+
end
|
27
|
+
|
28
|
+
def parameter
|
29
|
+
parameter = query_elements[2..-1].join(" ")
|
30
|
+
return if parameter.blank?
|
31
|
+
|
32
|
+
parameter.gsub(/"/, "")
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def sql_comparison_operator(element)
|
38
|
+
case element
|
39
|
+
when "eq"
|
40
|
+
"="
|
41
|
+
else
|
42
|
+
# TODO: implement additional query filters
|
43
|
+
raise ScimRails::ExceptionHandler::InvalidQuery
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
ScimRails::Engine.routes.draw do
|
2
|
+
get 'scim/v2/Users', action: :index, controller: 'scim_users'
|
3
|
+
post 'scim/v2/Users', action: :create, controller: 'scim_users'
|
4
|
+
get 'scim/v2/Users/:id', action: :show, controller: 'scim_users'
|
5
|
+
put 'scim/v2/Users/:id', action: :put_update, controller: 'scim_users'
|
6
|
+
patch 'scim/v2/Users/:id', action: :patch_update, controller: 'scim_users'
|
7
|
+
get 'scim/v2/Groups', action: :index, controller: 'scim_groups'
|
8
|
+
post 'scim/v2/Groups', action: :create, controller: 'scim_groups'
|
9
|
+
get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups'
|
10
|
+
put 'scim/v2/Groups/:id', action: :put_update, controller: 'scim_groups'
|
11
|
+
delete 'scim/v2/Groups/:id', action: :destroy, controller: 'scim_groups'
|
12
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ScimRails.configure do |config|
|
4
|
+
# Model used for authenticating and scoping users.
|
5
|
+
config.basic_auth_model = "Company"
|
6
|
+
|
7
|
+
# Attribute used to search for a given record. This
|
8
|
+
# attribute should be unique as it will return the
|
9
|
+
# first found record.
|
10
|
+
config.basic_auth_model_searchable_attribute = :subdomain
|
11
|
+
|
12
|
+
# Attribute used to compare Basic Auth password value.
|
13
|
+
# Attribute will need to return plaintext for comparison.
|
14
|
+
config.basic_auth_model_authenticatable_attribute = :api_token
|
15
|
+
|
16
|
+
# Model used for user records.
|
17
|
+
config.scim_users_model = "User"
|
18
|
+
|
19
|
+
# Method used for retrieving user records from the
|
20
|
+
# authenticatable model.
|
21
|
+
config.scim_users_scope = :users
|
22
|
+
|
23
|
+
# Determine whether the create endpoint updates users that already exist
|
24
|
+
# or throws an error (returning 409 Conflict in accordance with SCIM spec)
|
25
|
+
config.scim_user_prevent_update_on_create = false
|
26
|
+
|
27
|
+
# Model used for group records.
|
28
|
+
config.scim_groups_model = "Group"
|
29
|
+
# Method used for retrieving user records from the
|
30
|
+
# authenticatable model.
|
31
|
+
config.scim_groups_scope = :groups
|
32
|
+
|
33
|
+
# Cryptographic algorithm used for signing the auth tokens.
|
34
|
+
# It supports all algorithms supported by the jwt gem.
|
35
|
+
# See https://github.com/jwt/ruby-jwt#algorithms-and-usage for supported algorithms
|
36
|
+
# It is "none" by default, hence generated tokens are unsigned
|
37
|
+
# The tokens do not need to be signed if you only need basic authentication.
|
38
|
+
# config.signing_algorithm = "HS256"
|
39
|
+
|
40
|
+
# Secret token used to sign authorization tokens
|
41
|
+
# It is `nil` by default, hence generated tokens are unsigned
|
42
|
+
# The tokens do not need to be signed if you only need basic authentication.
|
43
|
+
# config.signing_secret = SECRET_TOKEN
|
44
|
+
|
45
|
+
# Default sort order for pagination is by id. If you
|
46
|
+
# use non sequential ids for user records, uncomment
|
47
|
+
# the below line and configure a determinate order.
|
48
|
+
# For example, [:created_at, :id] or { created_at: :desc }.
|
49
|
+
# config.scim_users_list_order = :id
|
50
|
+
|
51
|
+
# Method called on user model to deprovision a user.
|
52
|
+
config.user_deprovision_method = :archive!
|
53
|
+
|
54
|
+
# Method called on user model to reprovision a user.
|
55
|
+
config.user_reprovision_method = :unarchive!
|
56
|
+
|
57
|
+
# Hash of queryable attribtues on the user model. If
|
58
|
+
# the attribute is not listed in this hash it cannot
|
59
|
+
# be queried by this Gem. The structure of this hash
|
60
|
+
# is { queryable_scim_attribute => user_attribute }.
|
61
|
+
config.queryable_user_attributes = {
|
62
|
+
userName: :email,
|
63
|
+
givenName: :first_name,
|
64
|
+
familyName: :last_name,
|
65
|
+
email: :email
|
66
|
+
}
|
67
|
+
|
68
|
+
# Array of attributes that can be modified on the
|
69
|
+
# user model. If the attribute is not in this array
|
70
|
+
# the attribute cannot be modified by this Gem.
|
71
|
+
config.mutable_user_attributes = [
|
72
|
+
:first_name,
|
73
|
+
:last_name,
|
74
|
+
:email
|
75
|
+
]
|
76
|
+
|
77
|
+
# Hash of mutable attributes. This object is the map
|
78
|
+
# for this Gem to figure out where to look in a SCIM
|
79
|
+
# response for mutable values. This object should
|
80
|
+
# include all attributes listed in
|
81
|
+
# config.mutable_user_attributes.
|
82
|
+
config.mutable_user_attributes_schema = {
|
83
|
+
name: {
|
84
|
+
givenName: :first_name,
|
85
|
+
familyName: :last_name
|
86
|
+
},
|
87
|
+
emails: [
|
88
|
+
{
|
89
|
+
value: :email
|
90
|
+
}
|
91
|
+
]
|
92
|
+
}
|
93
|
+
|
94
|
+
# Hash of SCIM structure for a user schema. This object
|
95
|
+
# is what will be returned for a given user. The keys
|
96
|
+
# in this object should conform to standard SCIM
|
97
|
+
# structures. The values in the object will be
|
98
|
+
# transformed per user record. Strings will be passed
|
99
|
+
# through as is, symbols will be passed to the user
|
100
|
+
# object to return a value.
|
101
|
+
config.user_schema = {
|
102
|
+
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
103
|
+
id: :id,
|
104
|
+
userName: :email,
|
105
|
+
name: {
|
106
|
+
givenName: :first_name,
|
107
|
+
familyName: :last_name
|
108
|
+
},
|
109
|
+
emails: [
|
110
|
+
{
|
111
|
+
value: :email
|
112
|
+
}
|
113
|
+
],
|
114
|
+
active: :active?
|
115
|
+
}
|
116
|
+
|
117
|
+
# Schema for users used in "abbreviated" lists such as in
|
118
|
+
# the `members` field of a Group.
|
119
|
+
config.user_abbreviated_schema = {
|
120
|
+
value: :id,
|
121
|
+
display: :email
|
122
|
+
}
|
123
|
+
|
124
|
+
# Allow filtering Groups based on these parameters
|
125
|
+
config.queryable_group_attributes = {
|
126
|
+
displayName: :name
|
127
|
+
}
|
128
|
+
|
129
|
+
# List of attributes on a Group that can be updated through SCIM
|
130
|
+
config.mutable_group_attributes = [
|
131
|
+
:name
|
132
|
+
]
|
133
|
+
|
134
|
+
# Hash of mutable Group attributes. This object is the map
|
135
|
+
# for this Gem to figure out where to look in a SCIM
|
136
|
+
# response for mutable values. This object should
|
137
|
+
# include all attributes listed in
|
138
|
+
# config.mutable_group_attributes.
|
139
|
+
config.mutable_group_attributes_schema = {
|
140
|
+
displayName: :name
|
141
|
+
}
|
142
|
+
|
143
|
+
# The User relation's IDs field name on the Group model.
|
144
|
+
# Eg. if the relation is `has_many :users` this will be :user_ids
|
145
|
+
config.group_member_relation_attribute = :user_ids
|
146
|
+
# Which fields from the request's `members` field should be
|
147
|
+
# assigned to the relation IDs field. Should include the field
|
148
|
+
# set in config.group_member_relation_attribute.
|
149
|
+
config.group_member_relation_schema = { value: :user_ids }
|
150
|
+
|
151
|
+
config.group_schema = {
|
152
|
+
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
153
|
+
id: :id,
|
154
|
+
displayName: :name,
|
155
|
+
members: :users
|
156
|
+
}
|
157
|
+
|
158
|
+
config.group_abbreviated_schema = {
|
159
|
+
value: :id,
|
160
|
+
display: :name
|
161
|
+
}
|
162
|
+
|
163
|
+
# Set group_destroy_method to a method on the Group model
|
164
|
+
# to be called on a destroy request
|
165
|
+
# config.group_destroy_method = :destroy!
|
166
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ScimRails
|
4
|
+
class << self
|
5
|
+
def configure
|
6
|
+
yield config
|
7
|
+
end
|
8
|
+
|
9
|
+
def config
|
10
|
+
@config ||= Config.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Class containing configuration of ScimRails
|
15
|
+
class Config
|
16
|
+
ALGO_NONE = "none"
|
17
|
+
|
18
|
+
attr_writer \
|
19
|
+
:basic_auth_model,
|
20
|
+
:mutable_user_attributes_schema,
|
21
|
+
:mutable_group_attributes_schema,
|
22
|
+
:scim_users_model,
|
23
|
+
:scim_groups_model
|
24
|
+
|
25
|
+
attr_accessor \
|
26
|
+
:basic_auth_model_authenticatable_attribute,
|
27
|
+
:basic_auth_model_searchable_attribute,
|
28
|
+
:mutable_user_attributes,
|
29
|
+
:on_error,
|
30
|
+
:queryable_user_attributes,
|
31
|
+
:queryable_group_attributes,
|
32
|
+
:scim_users_list_order,
|
33
|
+
:scim_users_scope,
|
34
|
+
:scim_user_prevent_update_on_create,
|
35
|
+
:mutable_group_attributes,
|
36
|
+
:scim_groups_list_order,
|
37
|
+
:scim_groups_scope,
|
38
|
+
:group_member_relation_attribute,
|
39
|
+
:group_member_relation_schema,
|
40
|
+
:user_abbreviated_schema,
|
41
|
+
:group_abbreviated_schema,
|
42
|
+
:signing_secret,
|
43
|
+
:signing_algorithm,
|
44
|
+
:user_attributes,
|
45
|
+
:user_deprovision_method,
|
46
|
+
:user_reprovision_method,
|
47
|
+
:user_schema,
|
48
|
+
:group_schema,
|
49
|
+
:group_destroy_method
|
50
|
+
|
51
|
+
def initialize
|
52
|
+
@basic_auth_model = "Company"
|
53
|
+
@scim_users_list_order = :id
|
54
|
+
@scim_users_model = "User"
|
55
|
+
@scim_groups_list_order = :id
|
56
|
+
@scim_groups_model = "Group"
|
57
|
+
@signing_algorithm = ALGO_NONE
|
58
|
+
@user_schema = {}
|
59
|
+
@user_attributes = []
|
60
|
+
@user_abbreviated_schema = {}
|
61
|
+
@group_schema = {}
|
62
|
+
@group_abbreviated_schema = {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def mutable_user_attributes_schema
|
66
|
+
@mutable_user_attributes_schema || @user_schema
|
67
|
+
end
|
68
|
+
|
69
|
+
def mutable_group_attributes_schema
|
70
|
+
@mutable_group_attributes_schema || @group_schema
|
71
|
+
end
|
72
|
+
|
73
|
+
def basic_auth_model
|
74
|
+
@basic_auth_model.constantize
|
75
|
+
end
|
76
|
+
|
77
|
+
def scim_users_model
|
78
|
+
@scim_users_model.constantize
|
79
|
+
end
|
80
|
+
|
81
|
+
def scim_groups_model
|
82
|
+
@scim_groups_model.constantize
|
83
|
+
end
|
84
|
+
end
|
85
|
+
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ScimRails
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace ScimRails
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec, :fixture => false
|
7
|
+
g.fixture_replacement :factory_bot, :dir => "spec/factories"
|
8
|
+
g.assets false
|
9
|
+
g.helper false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|