scimaenaga 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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