scimaenaga 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +314 -0
  4. data/Rakefile +34 -0
  5. data/app/controllers/concerns/scim_rails/exception_handler.rb +119 -0
  6. data/app/controllers/concerns/scim_rails/response.rb +94 -0
  7. data/app/controllers/scim_rails/application_controller.rb +72 -0
  8. data/app/controllers/scim_rails/scim_groups_controller.rb +96 -0
  9. data/app/controllers/scim_rails/scim_users_controller.rb +124 -0
  10. data/app/helpers/scim_rails/application_helper.rb +4 -0
  11. data/app/models/scim_rails/application_record.rb +5 -0
  12. data/app/models/scim_rails/authorize_api_request.rb +40 -0
  13. data/app/models/scim_rails/scim_count.rb +38 -0
  14. data/app/models/scim_rails/scim_query_parser.rb +47 -0
  15. data/config/initializers/mime_types.rb +5 -0
  16. data/config/routes.rb +12 -0
  17. data/lib/generators/scim_rails/USAGE +8 -0
  18. data/lib/generators/scim_rails/scim_rails_generator.rb +7 -0
  19. data/lib/generators/scim_rails/templates/initializer.rb +166 -0
  20. data/lib/scim_rails/config.rb +85 -0
  21. data/lib/scim_rails/encoder.rb +25 -0
  22. data/lib/scim_rails/engine.rb +12 -0
  23. data/lib/scim_rails/version.rb +5 -0
  24. data/lib/scim_rails.rb +6 -0
  25. data/lib/tasks/scim_rails_tasks.rake +4 -0
  26. data/spec/controllers/scim_rails/scim_groups_controller_spec.rb +494 -0
  27. data/spec/controllers/scim_rails/scim_groups_request_spec.rb +68 -0
  28. data/spec/controllers/scim_rails/scim_users_controller_spec.rb +681 -0
  29. data/spec/controllers/scim_rails/scim_users_request_spec.rb +77 -0
  30. data/spec/dummy/Rakefile +6 -0
  31. data/spec/dummy/app/assets/config/manifest.js +5 -0
  32. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  33. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  34. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  35. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  36. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  37. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  38. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  39. data/spec/dummy/app/jobs/application_job.rb +2 -0
  40. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  41. data/spec/dummy/app/models/application_record.rb +3 -0
  42. data/spec/dummy/app/models/company.rb +4 -0
  43. data/spec/dummy/app/models/group.rb +15 -0
  44. data/spec/dummy/app/models/group_user.rb +6 -0
  45. data/spec/dummy/app/models/user.rb +39 -0
  46. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  47. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  48. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  49. data/spec/dummy/bin/bundle +3 -0
  50. data/spec/dummy/bin/rails +4 -0
  51. data/spec/dummy/bin/rake +4 -0
  52. data/spec/dummy/bin/setup +34 -0
  53. data/spec/dummy/bin/update +29 -0
  54. data/spec/dummy/config/application.rb +15 -0
  55. data/spec/dummy/config/boot.rb +5 -0
  56. data/spec/dummy/config/cable.yml +9 -0
  57. data/spec/dummy/config/database.yml +25 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +54 -0
  60. data/spec/dummy/config/environments/production.rb +86 -0
  61. data/spec/dummy/config/environments/test.rb +42 -0
  62. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  63. data/spec/dummy/config/initializers/assets.rb +11 -0
  64. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  65. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  66. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  67. data/spec/dummy/config/initializers/inflections.rb +16 -0
  68. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  69. data/spec/dummy/config/initializers/new_framework_defaults.rb +24 -0
  70. data/spec/dummy/config/initializers/scim_rails_config.rb +85 -0
  71. data/spec/dummy/config/initializers/session_store.rb +3 -0
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/dummy/config/locales/en.yml +23 -0
  74. data/spec/dummy/config/puma.rb +47 -0
  75. data/spec/dummy/config/routes.rb +3 -0
  76. data/spec/dummy/config/secrets.yml +22 -0
  77. data/spec/dummy/config/spring.rb +6 -0
  78. data/spec/dummy/config.ru +5 -0
  79. data/spec/dummy/db/migrate/20181206184304_create_users.rb +15 -0
  80. data/spec/dummy/db/migrate/20181206184313_create_companies.rb +11 -0
  81. data/spec/dummy/db/migrate/20210423075859_create_groups.rb +10 -0
  82. data/spec/dummy/db/migrate/20210423075950_create_group_users.rb +10 -0
  83. data/spec/dummy/db/schema.rb +53 -0
  84. data/spec/dummy/db/seeds.rb +14 -0
  85. data/spec/dummy/public/404.html +67 -0
  86. data/spec/dummy/public/422.html +67 -0
  87. data/spec/dummy/public/500.html +66 -0
  88. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  89. data/spec/dummy/public/apple-touch-icon.png +0 -0
  90. data/spec/dummy/public/favicon.ico +0 -0
  91. data/spec/factories/company.rb +10 -0
  92. data/spec/factories/group.rb +11 -0
  93. data/spec/factories/user.rb +9 -0
  94. data/spec/lib/scim_rails/encoder_spec.rb +62 -0
  95. data/spec/spec_helper.rb +17 -0
  96. data/spec/support/auth_helper.rb +7 -0
  97. data/spec/support/factory_bot.rb +3 -0
  98. data/spec/support/scim_rails_config.rb +59 -0
  99. 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,4 @@
1
+ module ScimRails
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module ScimRails
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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
@@ -0,0 +1,5 @@
1
+ Mime::Type.register "application/scim+json", :scimjson
2
+
3
+ ActionDispatch::Request.parameter_parsers[:scimjson] = lambda do |body|
4
+ ActiveSupport::JSON.decode(body)
5
+ 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,8 @@
1
+ Description:
2
+ Generates the scim_rails initializer.
3
+
4
+ Example:
5
+ rails generate scim_rails config
6
+
7
+ This will create:
8
+ config/initializers/scim_rails_config.rb
@@ -0,0 +1,7 @@
1
+ class ScimRailsGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('../templates', __FILE__)
3
+
4
+ def copy_initializer_file
5
+ copy_file "initializer.rb", "config/initializers/scim_rails_config.rb"
6
+ end
7
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScimRails
4
+ VERSION = "0.4.1"
5
+ end