From 50a4c5bb6e6f61b8fa658c7cf631a2e049cc9963 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:07:58 +0900 Subject: [PATCH 01/14] Move find_value_for and path_for to common ancestor --- .../scim_rails/application_controller.rb | 32 +++++++++++++++++++ .../scim_rails/scim_users_controller.rb | 32 ++----------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/app/controllers/scim_rails/application_controller.rb b/app/controllers/scim_rails/application_controller.rb index 2c2d09bb..59842811 100644 --- a/app/controllers/scim_rails/application_controller.rb +++ b/app/controllers/scim_rails/application_controller.rb @@ -34,5 +34,37 @@ def authenticate_with_oauth_bearer yield searchable_attribute, authentication_attribute end + + def find_value_for(attribute) + params.dig(*path_for(attribute)) + end + + # `path_for` is a recursive method used to find the "path" for + # `.dig` to take when looking for a given attribute in the + # params. + # + # Example: `path_for(:name)` should return an array that looks + # like [:names, 0, :givenName]. `.dig` can then use that path + # against the params to translate the :name attribute to "John". + + def path_for(attribute, object = controller_schema, path = []) + at_path = path.empty? ? object : object.dig(*path) + return path if at_path == attribute + + case at_path + when Hash + at_path.each do |key, value| + found_path = path_for(attribute, object, [*path, key]) + return found_path if found_path + end + nil + when Array + at_path.each_with_index do |value, index| + found_path = path_for(attribute, object, [*path, index]) + return found_path if found_path + end + nil + end + end end end diff --git a/app/controllers/scim_rails/scim_users_controller.rb b/app/controllers/scim_rails/scim_users_controller.rb index d0acb4fe..682bfc17 100644 --- a/app/controllers/scim_rails/scim_users_controller.rb +++ b/app/controllers/scim_rails/scim_users_controller.rb @@ -70,36 +70,8 @@ def permitted_user_params end end - def find_value_for(attribute) - params.dig(*path_for(attribute)) - end - - # `path_for` is a recursive method used to find the "path" for - # `.dig` to take when looking for a given attribute in the - # params. - # - # Example: `path_for(:name)` should return an array that looks - # like [:names, 0, :givenName]. `.dig` can then use that path - # against the params to translate the :name attribute to "John". - - def path_for(attribute, object = ScimRails.config.mutable_user_attributes_schema, path = []) - at_path = path.empty? ? object : object.dig(*path) - return path if at_path == attribute - - case at_path - when Hash - at_path.each do |key, value| - found_path = path_for(attribute, object, [*path, key]) - return found_path if found_path - end - nil - when Array - at_path.each_with_index do |value, index| - found_path = path_for(attribute, object, [*path, index]) - return found_path if found_path - end - nil - end + def controller_schema + ScimRails.config.mutable_user_attributes_schema end def update_status(user) From 6c49351698c6949f8ffe5c7d2d9ae0aea1b2b79b Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:10:42 +0900 Subject: [PATCH 02/14] Add Group-related config options --- .../scim_rails/templates/initializer.rb | 47 +++++++++++++++++++ lib/scim_rails/config.rb | 25 +++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/lib/generators/scim_rails/templates/initializer.rb b/lib/generators/scim_rails/templates/initializer.rb index 2cd7e503..a1aab7d2 100644 --- a/lib/generators/scim_rails/templates/initializer.rb +++ b/lib/generators/scim_rails/templates/initializer.rb @@ -22,6 +22,12 @@ # or throws an error (returning 409 Conflict in accordance with SCIM spec) config.scim_user_prevent_update_on_create = false + # Model used for group records. + config.scim_groups_model = 'Group' + # Method used for retrieving user records from the + # authenticatable model. + config.scim_groups_scope = :groups + # Cryptographic algorithm used for signing the auth tokens. # It supports all algorithms supported by the jwt gem. # See https://github.com/jwt/ruby-jwt#algorithms-and-usage for supported algorithms @@ -105,4 +111,45 @@ ], active: :active? } + + # Schema for users used in "abbreviated" lists such as in + # the `members` field of a Group. + config.user_abbreviated_schema = { + value: :id, + display: :email + } + + # List of attributes on a Group that can be updated through SCIM + config.mutable_group_attributes = [ + :name + ] + + # Hash of mutable Group attributes. This object is the map + # for this Gem to figure out where to look in a SCIM + # response for mutable values. This object should + # include all attributes listed in + # config.mutable_group_attributes. + config.mutable_group_attributes_schema = { + displayName: :name + } + + # The User relation's IDs field name on the Group model. + # Eg. if the relation is `has_many :users` this will be :user_ids + config.group_member_relation_attribute = :user_ids + # Which fields from the request's `members` field should be + # assigned to the relation IDs field. Should include the field + # set in config.group_member_relation_attribute. + config.group_member_relation_schema = { value: :user_ids } + + config.group_schema = { + schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"], + id: :id, + displayName: :name, + members: :users + } + + config.group_abbreviated_schema = { + value: :id, + display: :name + } end diff --git a/lib/scim_rails/config.rb b/lib/scim_rails/config.rb index aa4f108b..21af4e47 100644 --- a/lib/scim_rails/config.rb +++ b/lib/scim_rails/config.rb @@ -18,6 +18,7 @@ class Config attr_writer \ :basic_auth_model, :mutable_user_attributes_schema, + :mutable_group_attributes_schema, :scim_users_model attr_accessor \ @@ -29,26 +30,44 @@ class Config :scim_users_list_order, :scim_users_scope, :scim_user_prevent_update_on_create, + :mutable_group_attributes, + :scim_groups_list_order, + :scim_groups_model, + :scim_groups_scope, + :group_member_relation_attribute, + :group_member_relation_schema, + :user_abbreviated_schema, + :group_abbreviated_schema, :signing_secret, :signing_algorithm, :user_attributes, :user_deprovision_method, :user_reprovision_method, - :user_schema + :user_schema, + :group_schema def initialize @basic_auth_model = "Company" @scim_users_list_order = :id @scim_users_model = "User" + @scim_groups_list_order = :id + @scim_groups_model = 'Group' @signing_algorithm = ALGO_NONE @user_schema = {} @user_attributes = [] + @user_abbreviated_schema = {} + @group_schema = {} + @group_abbreviated_schema = {} end def mutable_user_attributes_schema @mutable_user_attributes_schema || @user_schema end + def mutable_group_attributes_schema + @mutable_group_attributes_schema || @group_schema + end + def basic_auth_model @basic_auth_model.constantize end @@ -56,5 +75,9 @@ def basic_auth_model def scim_users_model @scim_users_model.constantize end + + def scim_groups_model + @scim_groups_model.constantize + end end end From dc237ed298c0fc65eca1f74e152cd149f6965ebd Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:10:57 +0900 Subject: [PATCH 03/14] Prepare Response to deal with Groups --- .../concerns/scim_rails/response.rb | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/app/controllers/concerns/scim_rails/response.rb b/app/controllers/concerns/scim_rails/response.rb index 8fe1b3ea..dd4f43ed 100644 --- a/app/controllers/concerns/scim_rails/response.rb +++ b/app/controllers/concerns/scim_rails/response.rb @@ -18,7 +18,7 @@ def json_scim_response(object:, status: :ok, counts: nil) content_type: CONTENT_TYPE when "show", "create", "put_update", "patch_update" render \ - json: user_response(object), + json: object_response(object), status: status, content_type: CONTENT_TYPE end @@ -38,19 +38,28 @@ def list_response(object, counts) "totalResults": counts.total, "startIndex": counts.start_index, "itemsPerPage": counts.limit, - "Resources": list_users(object) + "Resources": list_objects(object) } end - def list_users(users) - users.map do |user| - user_response(user) + def list_objects(objects) + objects.map do |object| + object_response(object) end end - def user_response(user) - schema = ScimRails.config.user_schema - find_value(user, schema) + def object_response(object) + schema = + case object + when ScimRails.config.scim_users_model + ScimRails.config.user_schema + when ScimRails.config.scim_groups_model + ScimRails.config.group_schema + else + raise ScimRails::ExceptionHandler::InvalidQuery, + "Unknown model: #{object}" + end + find_value(object, schema) end @@ -61,20 +70,28 @@ def user_response(user) # send those symbols to the model, and replace the symbol with # the return value. - def find_value(user, object) - case object + def find_value(object, schema) + case schema when Hash - object.each.with_object({}) do |(key, value), hash| - hash[key] = find_value(user, value) + schema.each.with_object({}) do |(key, value), hash| + hash[key] = find_value(object, value) end - when Array - object.map do |value| - find_value(user, value) + when Array, ActiveRecord::Associations::CollectionProxy + schema.map do |value| + find_value(object, value) end + when ScimRails.config.scim_users_model + find_value(schema, ScimRails.config.user_abbreviated_schema) + when ScimRails.config.scim_groups_model + find_value(schema, ScimRails.config.group_abbreviated_schema) when Symbol - user.public_send(object) + value = object.public_send(schema) + case value + when true, false, String, Integer, DateTime then value + else find_value(object, value) + end else - object + schema end end end From 8b56c3037dfcc7618e56462813fbd925234b6a9c Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:11:06 +0900 Subject: [PATCH 04/14] Add Group endpoints --- .../scim_rails/scim_groups_controller.rb | 66 +++++++++++++++++++ config/routes.rb | 5 ++ 2 files changed, 71 insertions(+) create mode 100644 app/controllers/scim_rails/scim_groups_controller.rb diff --git a/app/controllers/scim_rails/scim_groups_controller.rb b/app/controllers/scim_rails/scim_groups_controller.rb new file mode 100644 index 00000000..d313005d --- /dev/null +++ b/app/controllers/scim_rails/scim_groups_controller.rb @@ -0,0 +1,66 @@ +module ScimRails + class ScimGroupsController < ScimRails::ApplicationController + def index + if params[:filter].present? + query = ScimRails::ScimQueryParser.new(params[:filter]) + + groups = @company + .public_send(ScimRails.config.scim_groups_scope) + .where( + "#{ScimRails.config.scim_groups_model.connection.quote_column_name(query.attribute)} #{query.operator} ?", + query.parameter + ) + .order(ScimRails.config.scim_groups_list_order) + else + groups = @company + .public_send(ScimRails.config.scim_groups_scope) + .preload(:users) + .order(ScimRails.config.scim_groups_list_order) + end + + counts = ScimCount.new( + start_index: params[:startIndex], + limit: params[:count], + total: groups.count + ) + + json_scim_response(object: groups, counts: counts) + end + + def show + group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) + json_scim_response(object: group) + end + + def create + group = @company + .public_send(ScimRails.config.scim_groups_scope) + .create!(permitted_group_params) + + json_scim_response(object: group, status: :created) + end + + def put_update + group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) + group.update!(permitted_group_params) + json_scim_response(object: group) + end + + private + + def permitted_group_params + ScimRails.config.mutable_group_attributes.each.with_object({}) do |attribute, hash| + hash[attribute] = find_value_for(attribute) + end.merge( + ScimRails.config.group_member_relation_attribute => + params[:members].map do |member| + member[ScimRails.config.group_member_relation_schema.keys.first] + end + ) + end + + def controller_schema + ScimRails.config.mutable_group_attributes_schema + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 3f2d16e1..459b3249 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,4 +4,9 @@ get 'scim/v2/Users/:id', action: :show, controller: 'scim_users' put 'scim/v2/Users/:id', action: :put_update, controller: 'scim_users' patch 'scim/v2/Users/:id', action: :patch_update, controller: 'scim_users' + get 'scim/v2/Groups', action: :index, controller: 'scim_groups' + post 'scim/v2/Groups', action: :create, controller: 'scim_groups' + get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups' + put 'scim/v2/Groups/:id', action: :put_update, controller: 'scim_groups' + end From 96a7e44ad96bdbe5f2fde184f213d6b742f04858 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:55:52 +0900 Subject: [PATCH 05/14] Add configuration option to destroy a Group --- lib/generators/scim_rails/templates/initializer.rb | 4 ++++ lib/scim_rails/config.rb | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/generators/scim_rails/templates/initializer.rb b/lib/generators/scim_rails/templates/initializer.rb index a1aab7d2..475b5202 100644 --- a/lib/generators/scim_rails/templates/initializer.rb +++ b/lib/generators/scim_rails/templates/initializer.rb @@ -152,4 +152,8 @@ value: :id, display: :name } + + # Set group_destroy_method to a method on the Group model + # to be called on a destroy request + # config.group_destroy_method = :destroy! end diff --git a/lib/scim_rails/config.rb b/lib/scim_rails/config.rb index 21af4e47..f39eb034 100644 --- a/lib/scim_rails/config.rb +++ b/lib/scim_rails/config.rb @@ -44,7 +44,8 @@ class Config :user_deprovision_method, :user_reprovision_method, :user_schema, - :group_schema + :group_schema, + :group_destroy_method def initialize @basic_auth_model = "Company" From 0a9ad7564a78f1a55d00536497033ba7c4e3ba22 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:56:22 +0900 Subject: [PATCH 06/14] Handle case if delete on Groups is not configured --- .../concerns/scim_rails/exception_handler.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/controllers/concerns/scim_rails/exception_handler.rb b/app/controllers/concerns/scim_rails/exception_handler.rb index f87cbc0a..21823613 100644 --- a/app/controllers/concerns/scim_rails/exception_handler.rb +++ b/app/controllers/concerns/scim_rails/exception_handler.rb @@ -11,6 +11,9 @@ class InvalidQuery < StandardError class UnsupportedPatchRequest < StandardError end + class UnsupportedDeleteRequest < StandardError + end + included do if Rails.env.production? rescue_from StandardError do |exception| @@ -65,6 +68,17 @@ class UnsupportedPatchRequest < StandardError ) end + rescue_from ScimRails::ExceptionHandler::UnsupportedDeleteRequest do + json_response( + { + schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"], + detail: "Delete operation is disabled for the requested resource.", + status: "501" + }, + :not_implemented + ) + end + rescue_from ActiveRecord::RecordNotFound do |e| json_response( { From 47cce8f5a40e8660c3f2d891a3cdfbced25c4eb7 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 15:56:37 +0900 Subject: [PATCH 07/14] Add Group deleting endpoint --- app/controllers/scim_rails/scim_groups_controller.rb | 9 +++++++++ config/routes.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/controllers/scim_rails/scim_groups_controller.rb b/app/controllers/scim_rails/scim_groups_controller.rb index d313005d..27114e01 100644 --- a/app/controllers/scim_rails/scim_groups_controller.rb +++ b/app/controllers/scim_rails/scim_groups_controller.rb @@ -46,6 +46,15 @@ def put_update json_scim_response(object: group) end + def destroy + unless ScimRails.config.group_destroy_method + raise ScimRails::ExceptionHandler::UnsupportedDeleteRequest + end + group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) + group.public_send(ScimRails.config.group_destroy_method) + json_response(nil, :no_content) + end + private def permitted_group_params diff --git a/config/routes.rb b/config/routes.rb index 459b3249..398477ba 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,5 +8,5 @@ post 'scim/v2/Groups', action: :create, controller: 'scim_groups' get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups' put 'scim/v2/Groups/:id', action: :put_update, controller: 'scim_groups' - + delete 'scim/v2/Groups/:id', action: :destroy, controller: 'scim_groups' end From 64965035890ad316c9d9fa8c4742bd57c858f188 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 18:53:13 +0900 Subject: [PATCH 08/14] Allow using QueryParser from Groups too --- app/controllers/scim_rails/scim_groups_controller.rb | 12 ++++++++---- app/controllers/scim_rails/scim_users_controller.rb | 4 +++- app/models/scim_rails/scim_query_parser.rb | 11 ++++------- lib/generators/scim_rails/templates/initializer.rb | 5 +++++ lib/scim_rails/config.rb | 1 + 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/controllers/scim_rails/scim_groups_controller.rb b/app/controllers/scim_rails/scim_groups_controller.rb index 27114e01..3a17914a 100644 --- a/app/controllers/scim_rails/scim_groups_controller.rb +++ b/app/controllers/scim_rails/scim_groups_controller.rb @@ -2,7 +2,9 @@ module ScimRails class ScimGroupsController < ScimRails::ApplicationController def index if params[:filter].present? - query = ScimRails::ScimQueryParser.new(params[:filter]) + query = ScimRails::ScimQueryParser.new( + params[:filter], ScimRails.config.queryable_group_attributes + ) groups = @company .public_send(ScimRails.config.scim_groups_scope) @@ -52,15 +54,17 @@ def destroy end group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) group.public_send(ScimRails.config.group_destroy_method) - json_response(nil, :no_content) + head :no_content end private def permitted_group_params - ScimRails.config.mutable_group_attributes.each.with_object({}) do |attribute, hash| + converted = ScimRails.config.mutable_group_attributes.each.with_object({}) do |attribute, hash| hash[attribute] = find_value_for(attribute) - end.merge( + end + return converted unless params[:members] + converted.merge( ScimRails.config.group_member_relation_attribute => params[:members].map do |member| member[ScimRails.config.group_member_relation_schema.keys.first] diff --git a/app/controllers/scim_rails/scim_users_controller.rb b/app/controllers/scim_rails/scim_users_controller.rb index 682bfc17..38899bcc 100644 --- a/app/controllers/scim_rails/scim_users_controller.rb +++ b/app/controllers/scim_rails/scim_users_controller.rb @@ -2,7 +2,9 @@ module ScimRails class ScimUsersController < ScimRails::ApplicationController def index if params[:filter].present? - query = ScimRails::ScimQueryParser.new(params[:filter]) + query = ScimRails::ScimQueryParser.new( + params[:filter], ScimRails.config.queryable_user_attributes + ) users = @company .public_send(ScimRails.config.scim_users_scope) diff --git a/app/models/scim_rails/scim_query_parser.rb b/app/models/scim_rails/scim_query_parser.rb index 969135d9..cdfa6198 100644 --- a/app/models/scim_rails/scim_query_parser.rb +++ b/app/models/scim_rails/scim_query_parser.rb @@ -1,9 +1,10 @@ module ScimRails class ScimQueryParser - attr_accessor :query_elements + attr_accessor :query_elements, :query_attributes - def initialize(query_string) + def initialize(query_string, queryable_attributes) self.query_elements = query_string.split(" ") + self.query_attributes = queryable_attributes end def attribute @@ -11,7 +12,7 @@ def attribute raise ScimRails::ExceptionHandler::InvalidQuery if attribute.blank? attribute = attribute.to_sym - mapped_attribute = attribute_mapping(attribute) + mapped_attribute = query_attributes[attribute] raise ScimRails::ExceptionHandler::InvalidQuery if mapped_attribute.blank? mapped_attribute end @@ -28,10 +29,6 @@ def parameter private - def attribute_mapping(attribute) - ScimRails.config.queryable_user_attributes[attribute] - end - def sql_comparison_operator(element) case element when "eq" diff --git a/lib/generators/scim_rails/templates/initializer.rb b/lib/generators/scim_rails/templates/initializer.rb index 475b5202..7e23876e 100644 --- a/lib/generators/scim_rails/templates/initializer.rb +++ b/lib/generators/scim_rails/templates/initializer.rb @@ -119,6 +119,11 @@ display: :email } + # Allow filtering Groups based on these parameters + config.queryable_group_attributes = { + displayName: :name + } + # List of attributes on a Group that can be updated through SCIM config.mutable_group_attributes = [ :name diff --git a/lib/scim_rails/config.rb b/lib/scim_rails/config.rb index f39eb034..503ebc34 100644 --- a/lib/scim_rails/config.rb +++ b/lib/scim_rails/config.rb @@ -27,6 +27,7 @@ class Config :mutable_user_attributes, :on_error, :queryable_user_attributes, + :queryable_group_attributes, :scim_users_list_order, :scim_users_scope, :scim_user_prevent_update_on_create, From fa1578e6e1d7506e2e048c016d61c7e7e9a9b0d0 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 18:53:41 +0900 Subject: [PATCH 09/14] Add Group and GroupUser to dummy app --- spec/dummy/app/models/company.rb | 1 + spec/dummy/app/models/group.rb | 13 +++++ spec/dummy/app/models/group_user.rb | 4 ++ spec/dummy/app/models/user.rb | 2 + .../config/initializers/scim_rails_config.rb | 29 +++++++++++ .../migrate/20210423075859_create_groups.rb | 10 ++++ .../20210423075950_create_group_users.rb | 10 ++++ spec/dummy/db/schema.rb | 50 +++++++++++++------ 8 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 spec/dummy/app/models/group.rb create mode 100644 spec/dummy/app/models/group_user.rb create mode 100644 spec/dummy/db/migrate/20210423075859_create_groups.rb create mode 100644 spec/dummy/db/migrate/20210423075950_create_group_users.rb diff --git a/spec/dummy/app/models/company.rb b/spec/dummy/app/models/company.rb index b3f5fc05..ffb52bdb 100644 --- a/spec/dummy/app/models/company.rb +++ b/spec/dummy/app/models/company.rb @@ -1,3 +1,4 @@ class Company < ApplicationRecord has_many :users + has_many :groups end diff --git a/spec/dummy/app/models/group.rb b/spec/dummy/app/models/group.rb new file mode 100644 index 00000000..9bc9a264 --- /dev/null +++ b/spec/dummy/app/models/group.rb @@ -0,0 +1,13 @@ +class Group < ApplicationRecord + belongs_to :company + has_many :group_users + has_many :users, through: :group_users + + validates \ + :name, + presence: true, + uniqueness: { + case_insensitive: true, + scope: :company + } +end diff --git a/spec/dummy/app/models/group_user.rb b/spec/dummy/app/models/group_user.rb new file mode 100644 index 00000000..866d8f2a --- /dev/null +++ b/spec/dummy/app/models/group_user.rb @@ -0,0 +1,4 @@ +class GroupUser < ApplicationRecord + belongs_to :group + belongs_to :user +end diff --git a/spec/dummy/app/models/user.rb b/spec/dummy/app/models/user.rb index b6173858..01f60d35 100644 --- a/spec/dummy/app/models/user.rb +++ b/spec/dummy/app/models/user.rb @@ -1,5 +1,7 @@ class User < ApplicationRecord belongs_to :company + has_many :group_users + has_many :groups, through: :group_users validates \ :first_name, diff --git a/spec/dummy/config/initializers/scim_rails_config.rb b/spec/dummy/config/initializers/scim_rails_config.rb index 2d9341bf..5af04c86 100644 --- a/spec/dummy/config/initializers/scim_rails_config.rb +++ b/spec/dummy/config/initializers/scim_rails_config.rb @@ -1,11 +1,13 @@ ScimRails.configure do |config| config.basic_auth_model = "Company" config.scim_users_model = "User" + config.scim_groups_model = "Group" config.basic_auth_model_searchable_attribute = :subdomain config.basic_auth_model_authenticatable_attribute = :api_token config.scim_users_scope = :users config.scim_users_list_order = :id + config.scim_groups_scope = :groups config.signing_algorithm = "HS256" config.signing_secret = "2d6806dd11c2fece2e81b8ca76dcb0062f5b08e28e3264e8ba1c44bbd3578b70" @@ -53,4 +55,31 @@ ], active: :unarchived? } + + config.queryable_group_attributes = { + displayName: :name + } + + config.mutable_group_attributes = [ + :name + ] + + config.mutable_group_attributes_schema = { + displayName: :name + } + + config.group_member_relation_attribute = :user_ids + config.group_member_relation_schema = { value: :user_ids } + + config.group_schema = { + schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"], + id: :id, + displayName: :name, + members: :users + } + + config.group_abbreviated_schema = { + value: :id, + display: :name + } end diff --git a/spec/dummy/db/migrate/20210423075859_create_groups.rb b/spec/dummy/db/migrate/20210423075859_create_groups.rb new file mode 100644 index 00000000..9934c043 --- /dev/null +++ b/spec/dummy/db/migrate/20210423075859_create_groups.rb @@ -0,0 +1,10 @@ +class CreateGroups < ActiveRecord::Migration[6.1] + def change + create_table :groups do |t| + t.string :name, null: false + t.references :company, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/spec/dummy/db/migrate/20210423075950_create_group_users.rb b/spec/dummy/db/migrate/20210423075950_create_group_users.rb new file mode 100644 index 00000000..51d0d63b --- /dev/null +++ b/spec/dummy/db/migrate/20210423075950_create_group_users.rb @@ -0,0 +1,10 @@ +class CreateGroupUsers < ActiveRecord::Migration[6.1] + def change + create_table :group_users do |t| + t.references :group, null: false, foreign_key: true + t.references :user, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 395e9b85..a184a664 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -2,32 +2,52 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181206184313) do +ActiveRecord::Schema.define(version: 2021_04_23_075950) do create_table "companies", force: :cascade do |t| - t.string "name", null: false - t.string "subdomain", null: false - t.string "api_token", null: false + t.string "name", null: false + t.string "subdomain", null: false + t.string "api_token", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end + create_table "group_users", force: :cascade do |t| + t.integer "group_id", null: false + t.integer "user_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["group_id"], name: "index_group_users_on_group_id" + t.index ["user_id"], name: "index_group_users_on_user_id" + end + + create_table "groups", force: :cascade do |t| + t.string "name", null: false + t.integer "company_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["company_id"], name: "index_groups_on_company_id" + end + create_table "users", force: :cascade do |t| - t.string "first_name", null: false - t.string "last_name", null: false - t.string "email", null: false - t.integer "company_id" + t.string "first_name", null: false + t.string "last_name", null: false + t.string "email", null: false + t.integer "company_id" t.datetime "archived_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end + add_foreign_key "group_users", "groups" + add_foreign_key "group_users", "users" + add_foreign_key "groups", "companies" end From dabd49c0d52fc4dd37d0271d80c735523c781d15 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 18:53:52 +0900 Subject: [PATCH 10/14] Add Group-related specs --- .../scim_rails/scim_groups_controller_spec.rb | 472 ++++++++++++++++++ .../scim_rails/scim_groups_request_spec.rb | 64 +++ .../scim_rails/scim_users_controller_spec.rb | 4 +- spec/factories/group.rb | 11 + 4 files changed, 549 insertions(+), 2 deletions(-) create mode 100644 spec/controllers/scim_rails/scim_groups_controller_spec.rb create mode 100644 spec/controllers/scim_rails/scim_groups_request_spec.rb create mode 100644 spec/factories/group.rb diff --git a/spec/controllers/scim_rails/scim_groups_controller_spec.rb b/spec/controllers/scim_rails/scim_groups_controller_spec.rb new file mode 100644 index 00000000..a8a4c848 --- /dev/null +++ b/spec/controllers/scim_rails/scim_groups_controller_spec.rb @@ -0,0 +1,472 @@ +require "spec_helper" + +RSpec.describe ScimRails::ScimGroupsController, type: :controller do + include AuthHelper + + routes { ScimRails::Engine.routes } + + describe "index" do + let(:company) { create(:company) } + + context "when unauthorized" do + it "returns scim+json content type" do + get :index, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "fails with no credentials" do + get :index, as: :json + + expect(response.status).to eq 401 + end + + it "fails with invalid credentials" do + request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + + get :index, as: :json + + expect(response.status).to eq 401 + end + end + + context "when authorized" do + before :each do + http_login(company) + end + + it "returns scim+json content type" do + get :index, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "is successful with valid credentials" do + get :index, as: :json + + expect(response.status).to eq 200 + end + + it "returns all results" do + create_list(:group, 5, company: company) + + get :index, as: :json + response_body = JSON.parse(response.body) + expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:ListResponse" + expect(response_body["totalResults"]).to eq 5 + end + + it "defaults to 100 results" do + create_list(:group, 300, company: company) + + get :index, as: :json + response_body = JSON.parse(response.body) + expect(response_body["totalResults"]).to eq 300 + expect(response_body["Resources"].count).to eq 100 + end + + it "paginates results" do + create_list(:group, 400, company: company) + expect(company.groups.first.id).to eq 1 + + get :index, params: { + startIndex: 101, + count: 200, + }, as: :json + response_body = JSON.parse(response.body) + expect(response_body["totalResults"]).to eq 400 + expect(response_body["Resources"].count).to eq 200 + expect(response_body.dig("Resources", 0, "id")).to eq 101 + end + + it "paginates results by configurable scim_groups_list_order" do + allow(ScimRails.config).to receive(:scim_groups_list_order).and_return({ created_at: :desc }) + + create_list(:group, 400, company: company) + expect(company.groups.first.id).to eq 1 + + get :index, params: { + startIndex: 1, + count: 10, + }, as: :json + response_body = JSON.parse(response.body) + expect(response_body["totalResults"]).to eq 400 + expect(response_body["Resources"].count).to eq 10 + expect(response_body.dig("Resources", 0, "id")).to eq 400 + end + + it "filters results by provided displayName filter" do + create(:group, name: "Foo", company: company) + create(:group, name: "Bar", company: company) + + get :index, params: { + filter: "displayName eq Bar" + }, as: :json + response_body = JSON.parse(response.body) + expect(response_body["totalResults"]).to eq 1 + expect(response_body["Resources"].count).to eq 1 + expect(response_body.dig("Resources", 0, "displayName")).to eq "Bar" + end + + it "returns no results for unfound filter parameters" do + get :index, params: { + filter: "displayName eq fake_not_there" + }, as: :json + response_body = JSON.parse(response.body) + expect(response_body["totalResults"]).to eq 0 + expect(response_body["Resources"].count).to eq 0 + end + + it "returns no results for undefined filter queries" do + get :index, params: { + filter: "address eq 101 Nowhere USA" + }, as: :json + expect(response.status).to eq 400 + response_body = JSON.parse(response.body) + expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error" + end + end + end + + describe "show" do + let(:company) { create(:company) } + + context "when unauthorized" do + it "returns scim+json content type" do + get :show, params: { id: 1 }, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "fails with no credentials" do + get :show, params: { id: 1 }, as: :json + + expect(response.status).to eq 401 + end + + it "fails with invalid credentials" do + request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + + get :show, params: { id: 1 }, as: :json + + expect(response.status).to eq 401 + end + end + + context "when authorized" do + before :each do + http_login(company) + end + + it "returns scim+json content type" do + get :show, params: { id: 1 }, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "is successful with valid credentials" do + create(:group, id: 1, company: company) + get :show, params: { id: 1 }, as: :json + + expect(response.status).to eq 200 + end + + it "returns :not_found for id that cannot be found" do + get :show, params: { id: "fake_id" }, as: :json + + expect(response.status).to eq 404 + end + + it "returns :not_found for a correct id but unauthorized company" do + new_company = create(:company) + create(:group, company: new_company, id: 1) + + get :show, params: { id: 1 }, as: :json + + expect(response.status).to eq 404 + end + end + end + + describe "create" do + let(:company) { create(:company) } + + context "when unauthorized" do + it "returns scim+json content type" do + post :create, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "fails with no credentials" do + post :create, as: :json + + expect(response.status).to eq 401 + end + + it "fails with invalid credentials" do + request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + + post :create, as: :json + + expect(response.status).to eq 401 + end + end + + context "when authorized" do + before :each do + http_login(company) + end + + it "returns scim+json content type" do + post :create, params: { + displayName: "Test Group", + members: [] + }, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "is successful with valid credentials" do + expect(company.groups.count).to eq 0 + + post :create, params: { + displayName: "Test Group", + members: [] + }, as: :json + + expect(response.status).to eq 201 + expect(company.groups.count).to eq 1 + group = company.groups.first + expect(group.persisted?).to eq true + expect(group.name).to eq "Test Group" + expect(group.users).to eq [] + end + + it "ignores unconfigured params" do + post :create, params: { + displayName: "Test Group", + department: "Best Department", + members: [] + }, as: :json + + expect(response.status).to eq 201 + expect(company.groups.count).to eq 1 + end + + it "returns 422 if required params are missing" do + post :create, params: { + members: [] + }, as: :json + + expect(response.status).to eq 422 + expect(company.users.count).to eq 0 + end + + it "returns 409 if group already exists" do + create(:group, name: "Test Group", company: company) + + post :create, params: { + displayName: "Test Group", + members: [] + }, as: :json + + expect(response.status).to eq 409 + expect(company.groups.count).to eq 1 + end + + it "creates group" do + users = create_list(:user, 3, company: company) + + post :create, params: { + displayName: "Test Group", + members: users.map do |user| + { value: user.id.to_s, display: user.email } + end + }, as: :json + + expect(response.status).to eq 201 + expect(company.groups.count).to eq 1 + group = company.groups.first + expect(group.name).to eq "Test Group" + expect(group.users.count).to eq 3 + end + end + end + + describe "put update" do + let(:company) { create(:company) } + + context "when unauthorized" do + it "returns scim+json content type" do + put :put_update, params: { id: 1 }, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "fails with no credentials" do + put :put_update, params: { id: 1 }, as: :json + + expect(response.status).to eq 401 + end + + it "fails with invalid credentials" do + request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + + put :put_update, params: { id: 1 }, as: :json + + expect(response.status).to eq 401 + end + end + + context "when authorized" do + let!(:group) { create(:group, id: 1, company: company) } + + before :each do + http_login(company) + end + + it "returns scim+json content type" do + put :put_update, params: put_params, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "is successful with with valid credentials" do + put :put_update, params: put_params, as: :json + + expect(response.status).to eq 200 + end + + it "can add and delete Users from a Group at once" do + user1 = create(:user, company: company, groups: [group]) + user2 = create(:user, company: company) + + expect do + put :put_update, params: put_params(users: [user2]), as: :json + end.to change { group.reload.users }.from([user1]).to([user2]) + + expect(response.status).to eq 200 + end + + it "returns :not_found for id that cannot be found" do + put :put_update, params: { id: "fake_id" }, as: :json + + expect(response.status).to eq 404 + end + + it "returns :not_found for a correct id but unauthorized company" do + new_company = create(:company) + create(:group, company: new_company, id: 1000) + + put :put_update, params: { id: 1000 }, as: :json + + expect(response.status).to eq 404 + end + + it "returns 422 with incomplete request" do + put :put_update, params: { + id: 1, + members: [] + }, as: :json + + expect(response.status).to eq 422 + end + end + end + + describe "destroy" do + let(:company) { create(:company) } + + context "when unauthorized" do + it "returns scim+json content type" do + delete :destroy, params: { id: 1 }, as: :json + + expect(response.media_type).to eq "application/scim+json" + end + + it "fails with no credentials" do + delete :destroy, params: { id: 1 }, as: :json + + expect(response.status).to eq 401 + end + + it "fails with invalid credentials" do + request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + + delete :destroy, params: { id: 1 }, as: :json + + expect(response.status).to eq 401 + end + end + + context "when authorized" do + let!(:group) { create(:group, id: 1, company: company) } + + before :each do + http_login(company) + end + + context "when Group destroy method is configured" do + before do + allow(ScimRails.config).to receive(:group_destroy_method).and_return(:destroy!) + end + + it "returns empty response" do + delete :destroy, params: { id: 1 }, as: :json + + expect(response.body).to be_empty + end + + it "is successful with valid credentials" do + delete :destroy, params: { id: 1 }, as: :json + + expect(response.status).to eq 204 + end + + it "returns :not_found for id that cannot be found" do + delete :destroy, params: { id: "fake_id" }, as: :json + + expect(response.status).to eq 404 + end + + it "returns :not_found for a correct id but unauthorized company" do + new_company = create(:company) + create(:group, company: new_company, id: 1000) + + delete :destroy, params: { id: 1000 }, as: :json + + expect(response.status).to eq 404 + end + + it "successfully deletes Group" do + expect do + delete :destroy, params: { id: 1 }, as: :json + end.to change { company.groups.reload.count }.from(1).to(0) + + expect(response.status).to eq 204 + end + end + + context "when Group destroy method is not configured" do + it "does not delete Group" do + allow(ScimRails.config).to receive(:group_destroy_method).and_return(nil) + + expect do + delete :destroy, params: { id: 1 }, as: :json + end.not_to change { company.groups.reload.count }.from(1) + + expect(response.status).to eq 501 + end + end + end + end + + def put_params(name: "Test Group", users: []) + { + id: 1, + displayName: name, + members: users.map { |user| { value: user.id.to_s, display: user.email } } + } + end +end diff --git a/spec/controllers/scim_rails/scim_groups_request_spec.rb b/spec/controllers/scim_rails/scim_groups_request_spec.rb new file mode 100644 index 00000000..5223c1c1 --- /dev/null +++ b/spec/controllers/scim_rails/scim_groups_request_spec.rb @@ -0,0 +1,64 @@ +require "spec_helper" + +RSpec.describe ScimRails::ScimGroupsController, type: :request do + let(:company) { create(:company) } + let(:credentials) { Base64::encode64("#{company.subdomain}:#{company.api_token}") } + let(:authorization) { "Basic #{credentials}" } + + def post_request(content_type = "application/scim+json") + post "/scim/v2/Groups", + params: { + displayName: "Dummy Group", + members: [] + }.to_json, + headers: { + 'Authorization': authorization, + 'Content-Type': content_type, + } + end + + describe "Content-Type" do + it "accepts scim+json" do + expect(company.groups.count).to eq 0 + + post_request("application/scim+json") + + expect(request.params).to include :displayName + expect(response.status).to eq 201 + expect(response.media_type).to eq "application/scim+json" + expect(company.groups.count).to eq 1 + end + + it "can not parse unfamiliar content types" do + expect(company.groups.count).to eq 0 + + post_request("text/csv") + + expect(request.params).not_to include :displayName + expect(response.status).to eq 422 + expect(company.groups.count).to eq 0 + end + end + + context "OAuth Bearer Authorization" do + context "with valid token" do + let(:authorization) { "Bearer #{company.api_token}" } + + it "supports OAuth bearer authorization and succeeds" do + expect { post_request }.to change(company.groups, :count).from(0).to(1) + + expect(response.status).to eq 201 + end + end + + context "with invalid token" do + let(:authorization) { "Bearer #{SecureRandom.hex}" } + + it "The request fails" do + expect { post_request }.not_to change(company.groups, :count) + + expect(response.status).to eq 401 + end + end + end +end diff --git a/spec/controllers/scim_rails/scim_users_controller_spec.rb b/spec/controllers/scim_rails/scim_users_controller_spec.rb index 03b9125d..0d79b91c 100644 --- a/spec/controllers/scim_rails/scim_users_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_users_controller_spec.rb @@ -433,7 +433,7 @@ end it "returns :not_found for id that cannot be found" do - get :put_update, params: { id: "fake_id" }, as: :json + put :put_update, params: { id: "fake_id" }, as: :json expect(response.status).to eq 404 end @@ -442,7 +442,7 @@ new_company = create(:company) create(:user, company: new_company, id: 1000) - get :put_update, params: { id: 1000 }, as: :json + put :put_update, params: { id: 1000 }, as: :json expect(response.status).to eq 404 end diff --git a/spec/factories/group.rb b/spec/factories/group.rb new file mode 100644 index 00000000..6ac6922d --- /dev/null +++ b/spec/factories/group.rb @@ -0,0 +1,11 @@ +FactoryBot.define do + factory :group do + company + + sequence(:name) { |i| "Test Group ##{i}" } + + trait :with_users do + users { create_list(:user) } + end + end +end From a42c69d6e61c64b06d70404d45c70fef1bf68338 Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 19:00:36 +0900 Subject: [PATCH 11/14] Fake old migration version for rails 5 CI --- spec/dummy/db/migrate/20210423075859_create_groups.rb | 2 +- spec/dummy/db/migrate/20210423075950_create_group_users.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/dummy/db/migrate/20210423075859_create_groups.rb b/spec/dummy/db/migrate/20210423075859_create_groups.rb index 9934c043..54566e90 100644 --- a/spec/dummy/db/migrate/20210423075859_create_groups.rb +++ b/spec/dummy/db/migrate/20210423075859_create_groups.rb @@ -1,4 +1,4 @@ -class CreateGroups < ActiveRecord::Migration[6.1] +class CreateGroups < ActiveRecord::Migration[5.0] def change create_table :groups do |t| t.string :name, null: false diff --git a/spec/dummy/db/migrate/20210423075950_create_group_users.rb b/spec/dummy/db/migrate/20210423075950_create_group_users.rb index 51d0d63b..7c5b7b14 100644 --- a/spec/dummy/db/migrate/20210423075950_create_group_users.rb +++ b/spec/dummy/db/migrate/20210423075950_create_group_users.rb @@ -1,4 +1,4 @@ -class CreateGroupUsers < ActiveRecord::Migration[6.1] +class CreateGroupUsers < ActiveRecord::Migration[5.0] def change create_table :group_users do |t| t.references :group, null: false, foreign_key: true From e0f1fa671b5067a393556e49f0f4cfd46552a10a Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 19:16:30 +0900 Subject: [PATCH 12/14] Fix lint warnings --- .rubocop.yml | 6 +++++ .../concerns/scim_rails/response.rb | 25 +++++++----------- .../scim_rails/application_controller.rb | 4 +-- .../scim_rails/scim_groups_controller.rb | 26 ++++++++++++++----- .../scim_rails/templates/initializer.rb | 2 +- lib/scim_rails/config.rb | 6 ++--- .../scim_rails/scim_groups_controller_spec.rb | 20 ++++++++++---- .../scim_rails/scim_users_controller_spec.rb | 20 ++++++++++---- 8 files changed, 72 insertions(+), 37 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6651a65c..b1a11239 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,2 +1,8 @@ inherit_from: - https://raw.githubusercontent.com/lessonly/rubocop-default-configuration/master/.rubocop.yml + +Metrics/BlockLength: + # don't warn about block length in block-centered DSLs + Exclude: + - 'config/routes.rb' + - 'spec/**/*.rb' diff --git a/app/controllers/concerns/scim_rails/response.rb b/app/controllers/concerns/scim_rails/response.rb index dd4f43ed..aae5e180 100644 --- a/app/controllers/concerns/scim_rails/response.rb +++ b/app/controllers/concerns/scim_rails/response.rb @@ -49,16 +49,15 @@ def list_objects(objects) end def object_response(object) - schema = - case object - when ScimRails.config.scim_users_model - ScimRails.config.user_schema - when ScimRails.config.scim_groups_model - ScimRails.config.group_schema - else - raise ScimRails::ExceptionHandler::InvalidQuery, - "Unknown model: #{object}" - end + schema = case object + when ScimRails.config.scim_users_model + ScimRails.config.user_schema + when ScimRails.config.scim_groups_model + ScimRails.config.group_schema + else + raise ScimRails::ExceptionHandler::InvalidQuery, + "Unknown model: #{object}" + end find_value(object, schema) end @@ -85,11 +84,7 @@ def find_value(object, schema) when ScimRails.config.scim_groups_model find_value(schema, ScimRails.config.group_abbreviated_schema) when Symbol - value = object.public_send(schema) - case value - when true, false, String, Integer, DateTime then value - else find_value(object, value) - end + find_value(object, object.public_send(schema)) else schema end diff --git a/app/controllers/scim_rails/application_controller.rb b/app/controllers/scim_rails/application_controller.rb index 59842811..6cd710b7 100644 --- a/app/controllers/scim_rails/application_controller.rb +++ b/app/controllers/scim_rails/application_controller.rb @@ -53,13 +53,13 @@ def path_for(attribute, object = controller_schema, path = []) case at_path when Hash - at_path.each do |key, value| + at_path.each do |key, _value| found_path = path_for(attribute, object, [*path, key]) return found_path if found_path end nil when Array - at_path.each_with_index do |value, index| + at_path.each_with_index do |_value, index| found_path = path_for(attribute, object, [*path, index]) return found_path if found_path end diff --git a/app/controllers/scim_rails/scim_groups_controller.rb b/app/controllers/scim_rails/scim_groups_controller.rb index 3a17914a..f12caf2a 100644 --- a/app/controllers/scim_rails/scim_groups_controller.rb +++ b/app/controllers/scim_rails/scim_groups_controller.rb @@ -30,7 +30,9 @@ def index end def show - group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) + group = @company + .public_send(ScimRails.config.scim_groups_scope) + .find(params[:id]) json_scim_response(object: group) end @@ -43,7 +45,9 @@ def create end def put_update - group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) + group = @company + .public_send(ScimRails.config.scim_groups_scope) + .find(params[:id]) group.update!(permitted_group_params) json_scim_response(object: group) end @@ -52,7 +56,9 @@ def destroy unless ScimRails.config.group_destroy_method raise ScimRails::ExceptionHandler::UnsupportedDeleteRequest end - group = @company.public_send(ScimRails.config.scim_groups_scope).find(params[:id]) + group = @company + .public_send(ScimRails.config.scim_groups_scope) + .find(params[:id]) group.public_send(ScimRails.config.group_destroy_method) head :no_content end @@ -60,16 +66,24 @@ def destroy private def permitted_group_params - converted = ScimRails.config.mutable_group_attributes.each.with_object({}) do |attribute, hash| + converted = mutable_attributes.each.with_object({}) do |attribute, hash| hash[attribute] = find_value_for(attribute) end return converted unless params[:members] - converted.merge( + converted.merge(member_params) + end + + def member_params + { ScimRails.config.group_member_relation_attribute => params[:members].map do |member| member[ScimRails.config.group_member_relation_schema.keys.first] end - ) + } + end + + def mutable_attributes + ScimRails.config.mutable_group_attributes end def controller_schema diff --git a/lib/generators/scim_rails/templates/initializer.rb b/lib/generators/scim_rails/templates/initializer.rb index 7e23876e..e496c107 100644 --- a/lib/generators/scim_rails/templates/initializer.rb +++ b/lib/generators/scim_rails/templates/initializer.rb @@ -23,7 +23,7 @@ config.scim_user_prevent_update_on_create = false # Model used for group records. - config.scim_groups_model = 'Group' + config.scim_groups_model = "Group" # Method used for retrieving user records from the # authenticatable model. config.scim_groups_scope = :groups diff --git a/lib/scim_rails/config.rb b/lib/scim_rails/config.rb index 503ebc34..6f229f57 100644 --- a/lib/scim_rails/config.rb +++ b/lib/scim_rails/config.rb @@ -19,7 +19,8 @@ class Config :basic_auth_model, :mutable_user_attributes_schema, :mutable_group_attributes_schema, - :scim_users_model + :scim_users_model, + :scim_groups_model attr_accessor \ :basic_auth_model_authenticatable_attribute, @@ -33,7 +34,6 @@ class Config :scim_user_prevent_update_on_create, :mutable_group_attributes, :scim_groups_list_order, - :scim_groups_model, :scim_groups_scope, :group_member_relation_attribute, :group_member_relation_schema, @@ -53,7 +53,7 @@ def initialize @scim_users_list_order = :id @scim_users_model = "User" @scim_groups_list_order = :id - @scim_groups_model = 'Group' + @scim_groups_model = "Group" @signing_algorithm = ALGO_NONE @user_schema = {} @user_attributes = [] diff --git a/spec/controllers/scim_rails/scim_groups_controller_spec.rb b/spec/controllers/scim_rails/scim_groups_controller_spec.rb index a8a4c848..17377968 100644 --- a/spec/controllers/scim_rails/scim_groups_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_groups_controller_spec.rb @@ -22,7 +22,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized", "123456") get :index, as: :json @@ -145,7 +147,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env['HTTP_AUTHORIZATION'] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized", "123456") get :show, params: { id: 1 }, as: :json @@ -392,7 +396,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env['HTTP_AUTHORIZATION'] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized", "123456") delete :destroy, params: { id: 1 }, as: :json @@ -409,7 +415,9 @@ context "when Group destroy method is configured" do before do - allow(ScimRails.config).to receive(:group_destroy_method).and_return(:destroy!) + allow(ScimRails.config).to( + receive(:group_destroy_method).and_return(:destroy!) + ) end it "returns empty response" do @@ -450,7 +458,9 @@ context "when Group destroy method is not configured" do it "does not delete Group" do - allow(ScimRails.config).to receive(:group_destroy_method).and_return(nil) + allow(ScimRails.config).to( + receive(:group_destroy_method).and_return(nil) + ) expect do delete :destroy, params: { id: 1 }, as: :json diff --git a/spec/controllers/scim_rails/scim_users_controller_spec.rb b/spec/controllers/scim_rails/scim_users_controller_spec.rb index 0d79b91c..064dfe87 100644 --- a/spec/controllers/scim_rails/scim_users_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_users_controller_spec.rb @@ -22,7 +22,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized","123456") get :index, as: :json @@ -157,7 +159,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized","123456") get :show, params: { id: 1 }, as: :json @@ -218,7 +222,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized","123456") post :create, as: :json @@ -387,7 +393,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized","123456") put :put_update, params: { id: 1 }, as: :json @@ -482,7 +490,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized","123456") patch :patch_update, params: patch_params(id: 1), as: :json From 8d65ade4ad227ab77f11b3b9e5419e94155655bc Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 19:27:15 +0900 Subject: [PATCH 13/14] Apply rubocop -A to all changed files --- .../concerns/scim_rails/exception_handler.rb | 2 ++ .../concerns/scim_rails/response.rb | 17 ++++----- .../scim_rails/application_controller.rb | 4 ++- .../scim_rails/scim_groups_controller.rb | 3 ++ .../scim_rails/scim_users_controller.rb | 4 ++- app/models/scim_rails/scim_query_parser.rb | 13 ++++--- .../scim_rails/templates/initializer.rb | 4 ++- .../scim_rails/scim_groups_controller_spec.rb | 36 ++++++++++++------- .../scim_rails/scim_groups_request_spec.rb | 8 +++-- .../scim_rails/scim_users_controller_spec.rb | 28 +++++++-------- .../scim_rails/scim_users_request_spec.rb | 16 +++++---- 11 files changed, 83 insertions(+), 52 deletions(-) diff --git a/app/controllers/concerns/scim_rails/exception_handler.rb b/app/controllers/concerns/scim_rails/exception_handler.rb index 21823613..a6621f12 100644 --- a/app/controllers/concerns/scim_rails/exception_handler.rb +++ b/app/controllers/concerns/scim_rails/exception_handler.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ScimRails module ExceptionHandler extend ActiveSupport::Concern diff --git a/app/controllers/concerns/scim_rails/response.rb b/app/controllers/concerns/scim_rails/response.rb index aae5e180..00bbbe67 100644 --- a/app/controllers/concerns/scim_rails/response.rb +++ b/app/controllers/concerns/scim_rails/response.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module ScimRails module Response - CONTENT_TYPE = "application/scim+json".freeze + CONTENT_TYPE = "application/scim+json" def json_response(object, status = :ok) render \ @@ -32,13 +34,13 @@ def list_response(object, counts) .offset(counts.offset) .limit(counts.limit) { - "schemas": [ - "urn:ietf:params:scim:api:messages:2.0:ListResponse" + schemas: [ + "urn:ietf:params:scim:api:messages:2.0:ListResponse" ], - "totalResults": counts.total, - "startIndex": counts.start_index, - "itemsPerPage": counts.limit, - "Resources": list_objects(object) + totalResults: counts.total, + startIndex: counts.start_index, + itemsPerPage: counts.limit, + Resources: list_objects(object) } end @@ -61,7 +63,6 @@ def object_response(object) find_value(object, schema) end - # `find_value` is a recursive method that takes a "user" and a # "user schema" and replaces any symbols in the schema with the # corresponding value from the user. Given a schema with symbols, diff --git a/app/controllers/scim_rails/application_controller.rb b/app/controllers/scim_rails/application_controller.rb index 6cd710b7..f9340d5c 100644 --- a/app/controllers/scim_rails/application_controller.rb +++ b/app/controllers/scim_rails/application_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ScimRails class ApplicationController < ActionController::API include ActionController::HttpAuthentication::Basic::ControllerMethods @@ -28,7 +30,7 @@ def authentication_strategy end def authenticate_with_oauth_bearer - authentication_attribute = request.headers["Authorization"].split(" ").last + authentication_attribute = request.headers["Authorization"].split.last payload = ScimRails::Encoder.decode(authentication_attribute).with_indifferent_access searchable_attribute = payload[ScimRails.config.basic_auth_model_searchable_attribute] diff --git a/app/controllers/scim_rails/scim_groups_controller.rb b/app/controllers/scim_rails/scim_groups_controller.rb index f12caf2a..6b798617 100644 --- a/app/controllers/scim_rails/scim_groups_controller.rb +++ b/app/controllers/scim_rails/scim_groups_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ScimRails class ScimGroupsController < ScimRails::ApplicationController def index @@ -70,6 +72,7 @@ def permitted_group_params hash[attribute] = find_value_for(attribute) end return converted unless params[:members] + converted.merge(member_params) end diff --git a/app/controllers/scim_rails/scim_users_controller.rb b/app/controllers/scim_rails/scim_users_controller.rb index 38899bcc..69c4dbc1 100644 --- a/app/controllers/scim_rails/scim_users_controller.rb +++ b/app/controllers/scim_rails/scim_users_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ScimRails class ScimUsersController < ScimRails::ApplicationController def index @@ -33,7 +35,7 @@ def create user = @company.public_send(ScimRails.config.scim_users_scope).create!(permitted_user_params) else username_key = ScimRails.config.queryable_user_attributes[:userName] - find_by_username = Hash.new + find_by_username = {} find_by_username[username_key] = permitted_user_params[username_key] user = @company .public_send(ScimRails.config.scim_users_scope) diff --git a/app/models/scim_rails/scim_query_parser.rb b/app/models/scim_rails/scim_query_parser.rb index cdfa6198..91db417c 100644 --- a/app/models/scim_rails/scim_query_parser.rb +++ b/app/models/scim_rails/scim_query_parser.rb @@ -1,29 +1,34 @@ +# frozen_string_literal: true + module ScimRails class ScimQueryParser attr_accessor :query_elements, :query_attributes def initialize(query_string, queryable_attributes) - self.query_elements = query_string.split(" ") + self.query_elements = query_string.split self.query_attributes = queryable_attributes end def attribute - attribute = query_elements.dig(0) + attribute = query_elements[0] raise ScimRails::ExceptionHandler::InvalidQuery if attribute.blank? + attribute = attribute.to_sym mapped_attribute = query_attributes[attribute] raise ScimRails::ExceptionHandler::InvalidQuery if mapped_attribute.blank? + mapped_attribute end def operator - sql_comparison_operator(query_elements.dig(1)) + sql_comparison_operator(query_elements[1]) end def parameter - parameter = query_elements[2..-1].join(" ") + parameter = query_elements[2..].join(" ") return if parameter.blank? + parameter.gsub(/"/, "") end diff --git a/lib/generators/scim_rails/templates/initializer.rb b/lib/generators/scim_rails/templates/initializer.rb index e496c107..5e045684 100644 --- a/lib/generators/scim_rails/templates/initializer.rb +++ b/lib/generators/scim_rails/templates/initializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ScimRails.configure do |config| # Model used for authenticating and scoping users. config.basic_auth_model = "Company" @@ -107,7 +109,7 @@ emails: [ { value: :email - }, + } ], active: :active? } diff --git a/spec/controllers/scim_rails/scim_groups_controller_spec.rb b/spec/controllers/scim_rails/scim_groups_controller_spec.rb index 17377968..aa0ed85f 100644 --- a/spec/controllers/scim_rails/scim_groups_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_groups_controller_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe ScimRails::ScimGroupsController, type: :controller do @@ -24,7 +26,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") get :index, as: :json @@ -54,7 +56,9 @@ get :index, as: :json response_body = JSON.parse(response.body) - expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:ListResponse" + expect(response_body.dig("schemas", 0)).to( + eq "urn:ietf:params:scim:api:messages:2.0:ListResponse" + ) expect(response_body["totalResults"]).to eq 5 end @@ -73,7 +77,7 @@ get :index, params: { startIndex: 101, - count: 200, + count: 200 }, as: :json response_body = JSON.parse(response.body) expect(response_body["totalResults"]).to eq 400 @@ -82,14 +86,16 @@ end it "paginates results by configurable scim_groups_list_order" do - allow(ScimRails.config).to receive(:scim_groups_list_order).and_return({ created_at: :desc }) + allow(ScimRails.config).to( + receive(:scim_groups_list_order).and_return(created_at: :desc) + ) create_list(:group, 400, company: company) expect(company.groups.first.id).to eq 1 get :index, params: { startIndex: 1, - count: 10, + count: 10 }, as: :json response_body = JSON.parse(response.body) expect(response_body["totalResults"]).to eq 400 @@ -125,7 +131,9 @@ }, as: :json expect(response.status).to eq 400 response_body = JSON.parse(response.body) - expect(response_body.dig("schemas", 0)).to eq "urn:ietf:params:scim:api:messages:2.0:Error" + expect(response_body.dig("schemas", 0)).to( + eq "urn:ietf:params:scim:api:messages:2.0:Error" + ) end end end @@ -147,9 +155,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = + request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") get :show, params: { id: 1 }, as: :json @@ -209,7 +217,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized", "123456") post :create, as: :json @@ -315,7 +325,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials("unauthorized","123456") + request.env["HTTP_AUTHORIZATION"] = + ActionController::HttpAuthentication::Basic + .encode_credentials("unauthorized", "123456") put :put_update, params: { id: 1 }, as: :json @@ -396,9 +408,9 @@ end it "fails with invalid credentials" do - request.env['HTTP_AUTHORIZATION'] = + request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") delete :destroy, params: { id: 1 }, as: :json diff --git a/spec/controllers/scim_rails/scim_groups_request_spec.rb b/spec/controllers/scim_rails/scim_groups_request_spec.rb index 5223c1c1..2e7b2e46 100644 --- a/spec/controllers/scim_rails/scim_groups_request_spec.rb +++ b/spec/controllers/scim_rails/scim_groups_request_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe ScimRails::ScimGroupsController, type: :request do let(:company) { create(:company) } - let(:credentials) { Base64::encode64("#{company.subdomain}:#{company.api_token}") } + let(:credentials) { Base64.encode64("#{company.subdomain}:#{company.api_token}") } let(:authorization) { "Basic #{credentials}" } def post_request(content_type = "application/scim+json") @@ -12,8 +14,8 @@ def post_request(content_type = "application/scim+json") members: [] }.to_json, headers: { - 'Authorization': authorization, - 'Content-Type': content_type, + Authorization: authorization, + 'Content-Type': content_type } end diff --git a/spec/controllers/scim_rails/scim_users_controller_spec.rb b/spec/controllers/scim_rails/scim_users_controller_spec.rb index 064dfe87..b7383271 100644 --- a/spec/controllers/scim_rails/scim_users_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_users_controller_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe ScimRails::ScimUsersController, type: :controller do @@ -24,7 +26,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized","123456") + .encode_credentials("unauthorized", "123456") get :index, as: :json @@ -73,7 +75,7 @@ get :index, params: { startIndex: 101, - count: 200, + count: 200 }, as: :json response_body = JSON.parse(response.body) expect(response_body["totalResults"]).to eq 400 @@ -89,7 +91,7 @@ get :index, params: { startIndex: 1, - count: 10, + count: 10 }, as: :json response_body = JSON.parse(response.body) expect(response_body["totalResults"]).to eq 400 @@ -141,7 +143,6 @@ end end - describe "show" do let(:company) { create(:company) } @@ -161,7 +162,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized","123456") + .encode_credentials("unauthorized", "123456") get :show, params: { id: 1 }, as: :json @@ -204,7 +205,6 @@ end end - describe "create" do let(:company) { create(:company) } @@ -224,7 +224,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized","123456") + .encode_credentials("unauthorized", "123456") post :create, as: :json @@ -362,7 +362,7 @@ emails: [ { value: "test@example.com" - }, + } ], active: "false" }, as: :json @@ -375,7 +375,6 @@ end end - describe "put update" do let(:company) { create(:company) } @@ -395,7 +394,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized","123456") + .encode_credentials("unauthorized", "123456") put :put_update, params: { id: 1 }, as: :json @@ -462,7 +461,7 @@ emails: [ { value: "test@example.com" - }, + } ], active: "true" }, as: :json @@ -472,7 +471,6 @@ end end - describe "patch update" do let(:company) { create(:company) } @@ -492,7 +490,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized","123456") + .encode_credentials("unauthorized", "123456") patch :patch_update, params: patch_params(id: 1), as: :json @@ -552,7 +550,7 @@ user = company.users.first.tap(&:archive!) expect(user.archived?).to eq true - patch :patch_update, params: patch_params(id: 1, active: true), as: :json + patch :patch_update, params: patch_params(id: 1, active: true), as: :json expect(response.status).to eq 200 expect(company.users.count).to eq 1 @@ -672,7 +670,7 @@ def put_params(active: true) emails: [ { value: "test@example.com" - }, + } ], active: active } diff --git a/spec/controllers/scim_rails/scim_users_request_spec.rb b/spec/controllers/scim_rails/scim_users_request_spec.rb index 7e9168e3..975923fc 100644 --- a/spec/controllers/scim_rails/scim_users_request_spec.rb +++ b/spec/controllers/scim_rails/scim_users_request_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require "spec_helper" RSpec.describe ScimRails::ScimUsersController, type: :request do let(:company) { create(:company) } - let(:credentials) { Base64::encode64("#{company.subdomain}:#{company.api_token}") } + let(:credentials) { Base64.encode64("#{company.subdomain}:#{company.api_token}") } let(:authorization) { "Basic #{credentials}" } def post_request(content_type = "application/scim+json") @@ -12,17 +14,17 @@ def post_request(content_type = "application/scim+json") params: { name: { givenName: "New", - familyName: "User", + familyName: "User" }, emails: [ { - value: "new@example.com", - }, - ], + value: "new@example.com" + } + ] }.to_json, headers: { - 'Authorization': authorization, - 'Content-Type': content_type, + Authorization: authorization, + 'Content-Type': content_type } end From f3425e22f6e2b23fbbb146d095631c61184ee0ca Mon Sep 17 00:00:00 2001 From: Vale Date: Fri, 23 Apr 2021 19:31:38 +0900 Subject: [PATCH 14/14] Fix further lint warnings --- app/models/scim_rails/scim_query_parser.rb | 2 +- .../scim_rails/scim_groups_controller_spec.rb | 10 +++++----- .../scim_rails/scim_groups_request_spec.rb | 4 +++- .../scim_rails/scim_users_controller_spec.rb | 15 +++++++++------ .../scim_rails/scim_users_request_spec.rb | 4 +++- spec/dummy/app/models/group.rb | 2 ++ spec/dummy/app/models/group_user.rb | 2 ++ 7 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/models/scim_rails/scim_query_parser.rb b/app/models/scim_rails/scim_query_parser.rb index 91db417c..c0475dfa 100644 --- a/app/models/scim_rails/scim_query_parser.rb +++ b/app/models/scim_rails/scim_query_parser.rb @@ -26,7 +26,7 @@ def operator end def parameter - parameter = query_elements[2..].join(" ") + parameter = query_elements[2..-1].join(" ") return if parameter.blank? parameter.gsub(/"/, "") diff --git a/spec/controllers/scim_rails/scim_groups_controller_spec.rb b/spec/controllers/scim_rails/scim_groups_controller_spec.rb index aa0ed85f..e168eac7 100644 --- a/spec/controllers/scim_rails/scim_groups_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_groups_controller_spec.rb @@ -26,7 +26,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") get :index, as: :json @@ -157,7 +157,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") get :show, params: { id: 1 }, as: :json @@ -219,7 +219,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") post :create, as: :json @@ -327,7 +327,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") put :put_update, params: { id: 1 }, as: :json @@ -410,7 +410,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") delete :destroy, params: { id: 1 }, as: :json diff --git a/spec/controllers/scim_rails/scim_groups_request_spec.rb b/spec/controllers/scim_rails/scim_groups_request_spec.rb index 2e7b2e46..be40bee1 100644 --- a/spec/controllers/scim_rails/scim_groups_request_spec.rb +++ b/spec/controllers/scim_rails/scim_groups_request_spec.rb @@ -4,7 +4,9 @@ RSpec.describe ScimRails::ScimGroupsController, type: :request do let(:company) { create(:company) } - let(:credentials) { Base64.encode64("#{company.subdomain}:#{company.api_token}") } + let(:credentials) do + Base64.encode64("#{company.subdomain}:#{company.api_token}") + end let(:authorization) { "Basic #{credentials}" } def post_request(content_type = "application/scim+json") diff --git a/spec/controllers/scim_rails/scim_users_controller_spec.rb b/spec/controllers/scim_rails/scim_users_controller_spec.rb index b7383271..a9fbfbea 100644 --- a/spec/controllers/scim_rails/scim_users_controller_spec.rb +++ b/spec/controllers/scim_rails/scim_users_controller_spec.rb @@ -26,7 +26,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") get :index, as: :json @@ -162,7 +162,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") get :show, params: { id: 1 }, as: :json @@ -224,7 +224,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") post :create, as: :json @@ -394,7 +394,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") put :put_update, params: { id: 1 }, as: :json @@ -490,7 +490,7 @@ it "fails with invalid credentials" do request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic - .encode_credentials("unauthorized", "123456") + .encode_credentials("unauthorized", "123456") patch :patch_update, params: patch_params(id: 1), as: :json @@ -550,7 +550,10 @@ user = company.users.first.tap(&:archive!) expect(user.archived?).to eq true - patch :patch_update, params: patch_params(id: 1, active: true), as: :json + patch \ + :patch_update, + params: patch_params(id: 1, active: true), + as: :json expect(response.status).to eq 200 expect(company.users.count).to eq 1 diff --git a/spec/controllers/scim_rails/scim_users_request_spec.rb b/spec/controllers/scim_rails/scim_users_request_spec.rb index 975923fc..41a75c2e 100644 --- a/spec/controllers/scim_rails/scim_users_request_spec.rb +++ b/spec/controllers/scim_rails/scim_users_request_spec.rb @@ -4,7 +4,9 @@ RSpec.describe ScimRails::ScimUsersController, type: :request do let(:company) { create(:company) } - let(:credentials) { Base64.encode64("#{company.subdomain}:#{company.api_token}") } + let(:credentials) do + Base64.encode64("#{company.subdomain}:#{company.api_token}") + end let(:authorization) { "Basic #{credentials}" } def post_request(content_type = "application/scim+json") diff --git a/spec/dummy/app/models/group.rb b/spec/dummy/app/models/group.rb index 9bc9a264..379a3c96 100644 --- a/spec/dummy/app/models/group.rb +++ b/spec/dummy/app/models/group.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Group < ApplicationRecord belongs_to :company has_many :group_users diff --git a/spec/dummy/app/models/group_user.rb b/spec/dummy/app/models/group_user.rb index 866d8f2a..b36e9105 100644 --- a/spec/dummy/app/models/group_user.rb +++ b/spec/dummy/app/models/group_user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GroupUser < ApplicationRecord belongs_to :group belongs_to :user