scimaenaga 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -8
  3. data/Rakefile +6 -8
  4. data/app/controllers/concerns/{scim_rails → scimaenaga}/exception_handler.rb +10 -10
  5. data/app/controllers/concerns/scimaenaga/response.rb +94 -0
  6. data/app/controllers/scimaenaga/application_controller.rb +72 -0
  7. data/app/controllers/{scim_rails → scimaenaga}/scim_groups_controller.rb +25 -25
  8. data/app/controllers/{scim_rails → scimaenaga}/scim_schemas_controller.rb +5 -5
  9. data/app/controllers/scimaenaga/scim_users_controller.rb +104 -0
  10. data/app/helpers/{scim_rails → scimaenaga}/application_helper.rb +1 -1
  11. data/app/libraries/scim_patch.rb +2 -2
  12. data/app/libraries/scim_patch_operation.rb +1 -1
  13. data/app/libraries/scim_patch_operation_group.rb +3 -3
  14. data/app/libraries/scim_patch_operation_user.rb +2 -2
  15. data/app/models/{scim_rails → scimaenaga}/application_record.rb +1 -1
  16. data/app/models/scimaenaga/authorize_api_request.rb +39 -0
  17. data/app/models/{scim_rails → scimaenaga}/scim_count.rb +8 -4
  18. data/app/models/scimaenaga/scim_query_parser.rb +49 -0
  19. data/config/routes.rb +1 -1
  20. data/lib/generators/scimaenaga/USAGE +8 -0
  21. data/lib/generators/scimaenaga/scimaenaga_generator.rb +7 -0
  22. data/lib/generators/{scim_rails → scimaenaga}/templates/initializer.rb +22 -22
  23. data/lib/{scim_rails → scimaenaga}/config.rb +2 -2
  24. data/lib/scimaenaga/encoder.rb +27 -0
  25. data/lib/scimaenaga/engine.rb +12 -0
  26. data/lib/scimaenaga/version.rb +5 -0
  27. data/lib/scimaenaga.rb +6 -0
  28. data/lib/tasks/{scim_rails_tasks.rake → scimaenaga_tasks.rake} +1 -1
  29. data/spec/controllers/{scim_rails → scimaenaga}/scim_groups_controller_spec.rb +8 -8
  30. data/spec/controllers/{scim_rails → scimaenaga}/scim_groups_request_spec.rb +18 -18
  31. data/spec/controllers/{scim_rails → scimaenaga}/scim_schemas_controller_spec.rb +7 -7
  32. data/spec/controllers/{scim_rails → scimaenaga}/scim_schemas_request_spec.rb +1 -1
  33. data/spec/controllers/{scim_rails → scimaenaga}/scim_users_controller_spec.rb +14 -15
  34. data/spec/controllers/{scim_rails → scimaenaga}/scim_users_request_spec.rb +20 -20
  35. data/spec/dummy/app/assets/config/manifest.js +1 -1
  36. data/spec/dummy/config/application.rb +1 -2
  37. data/spec/dummy/config/initializers/{scim_rails_config.rb → scimaenaga_config.rb} +1 -1
  38. data/spec/dummy/config/routes.rb +1 -1
  39. data/spec/factories/company.rb +3 -3
  40. data/spec/lib/scimaenaga/encoder_spec.rb +64 -0
  41. data/spec/libraries/scim_patch_operation_group_spec.rb +14 -14
  42. data/spec/libraries/scim_patch_operation_user_spec.rb +5 -5
  43. data/spec/libraries/scim_patch_spec.rb +2 -2
  44. data/spec/models/scim_query_parser_spec.rb +5 -6
  45. metadata +40 -39
  46. data/app/controllers/concerns/scim_rails/response.rb +0 -94
  47. data/app/controllers/scim_rails/application_controller.rb +0 -72
  48. data/app/controllers/scim_rails/scim_users_controller.rb +0 -104
  49. data/app/models/scim_rails/authorize_api_request.rb +0 -40
  50. data/app/models/scim_rails/scim_query_parser.rb +0 -49
  51. data/lib/generators/scim_rails/USAGE +0 -8
  52. data/lib/generators/scim_rails/scim_rails_generator.rb +0 -7
  53. data/lib/scim_rails/encoder.rb +0 -25
  54. data/lib/scim_rails/engine.rb +0 -12
  55. data/lib/scim_rails/version.rb +0 -5
  56. data/lib/scim_rails.rb +0 -6
  57. data/spec/lib/scim_rails/encoder_spec.rb +0 -62
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ScimRails
4
- module Response
5
- CONTENT_TYPE = "application/scim+json"
6
-
7
- def json_response(object, status = :ok)
8
- render \
9
- json: object,
10
- status: status,
11
- content_type: CONTENT_TYPE
12
- end
13
-
14
- def json_scim_response(object:, status: :ok, counts: nil)
15
- case params[:action]
16
- when "index"
17
- render \
18
- json: list_response(object, counts),
19
- status: status,
20
- content_type: CONTENT_TYPE
21
- when "show", "create", "put_update", "patch_update"
22
- render \
23
- json: object_response(object),
24
- status: status,
25
- content_type: CONTENT_TYPE
26
- end
27
- end
28
-
29
- private
30
-
31
- def list_response(object, counts)
32
- object = object
33
- .order(:id)
34
- .offset(counts.offset)
35
- .limit(counts.limit)
36
- {
37
- schemas: [
38
- "urn:ietf:params:scim:api:messages:2.0:ListResponse"
39
- ],
40
- totalResults: counts.total,
41
- startIndex: counts.start_index,
42
- itemsPerPage: counts.limit,
43
- Resources: list_objects(object)
44
- }
45
- end
46
-
47
- def list_objects(objects)
48
- objects.map do |object|
49
- object_response(object)
50
- end
51
- end
52
-
53
- def object_response(object)
54
- schema = case object
55
- when ScimRails.config.scim_users_model
56
- ScimRails.config.user_schema
57
- when ScimRails.config.scim_groups_model
58
- ScimRails.config.group_schema
59
- else
60
- raise ScimRails::ExceptionHandler::InvalidQuery,
61
- "Unknown model: #{object}"
62
- end
63
- find_value(object, schema)
64
- end
65
-
66
- # `find_value` is a recursive method that takes a "user" and a
67
- # "user schema" and replaces any symbols in the schema with the
68
- # corresponding value from the user. Given a schema with symbols,
69
- # `find_value` will search through the object for the symbols,
70
- # send those symbols to the model, and replace the symbol with
71
- # the return value.
72
-
73
- def find_value(object, schema)
74
- case schema
75
- when Hash
76
- schema.each.with_object({}) do |(key, value), hash|
77
- hash[key] = find_value(object, value)
78
- end
79
- when Array, ActiveRecord::Associations::CollectionProxy
80
- schema.map do |value|
81
- find_value(object, value)
82
- end
83
- when ScimRails.config.scim_users_model
84
- find_value(schema, ScimRails.config.user_abbreviated_schema)
85
- when ScimRails.config.scim_groups_model
86
- find_value(schema, ScimRails.config.group_abbreviated_schema)
87
- when Symbol
88
- find_value(object, object.public_send(schema))
89
- else
90
- schema
91
- end
92
- end
93
- end
94
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ScimRails
4
- class ApplicationController < ActionController::API
5
- include ActionController::HttpAuthentication::Basic::ControllerMethods
6
- include ExceptionHandler
7
- include Response
8
-
9
- before_action :authorize_request
10
-
11
- private
12
-
13
- def authorize_request
14
- send(authentication_strategy) do |searchable_attribute, authentication_attribute|
15
- authorization = AuthorizeApiRequest.new(
16
- searchable_attribute: searchable_attribute,
17
- authentication_attribute: authentication_attribute
18
- )
19
- @company = authorization.company
20
- end
21
- raise ScimRails::ExceptionHandler::InvalidCredentials if @company.blank?
22
- end
23
-
24
- def authentication_strategy
25
- if request.headers["Authorization"]&.include?("Bearer")
26
- :authenticate_with_oauth_bearer
27
- else
28
- :authenticate_with_http_basic
29
- end
30
- end
31
-
32
- def authenticate_with_oauth_bearer
33
- authentication_attribute = request.headers["Authorization"].split.last
34
- payload = ScimRails::Encoder.decode(authentication_attribute).with_indifferent_access
35
- searchable_attribute = payload[ScimRails.config.basic_auth_model_searchable_attribute]
36
-
37
- yield searchable_attribute, authentication_attribute
38
- end
39
-
40
- def find_value_for(attribute)
41
- params.dig(*path_for(attribute))
42
- end
43
-
44
- # `path_for` is a recursive method used to find the "path" for
45
- # `.dig` to take when looking for a given attribute in the
46
- # params.
47
- #
48
- # Example: `path_for(:name)` should return an array that looks
49
- # like [:names, 0, :givenName]. `.dig` can then use that path
50
- # against the params to translate the :name attribute to "John".
51
-
52
- def path_for(attribute, object = controller_schema, path = [])
53
- at_path = path.empty? ? object : object.dig(*path)
54
- return path if at_path == attribute
55
-
56
- case at_path
57
- when Hash
58
- at_path.each do |key, _value|
59
- found_path = path_for(attribute, object, [*path, key])
60
- return found_path if found_path
61
- end
62
- nil
63
- when Array
64
- at_path.each_with_index do |_value, index|
65
- found_path = path_for(attribute, object, [*path, index])
66
- return found_path if found_path
67
- end
68
- nil
69
- end
70
- end
71
- end
72
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ScimRails
4
- class ScimUsersController < ScimRails::ApplicationController
5
-
6
- def index
7
- if params[:filter].present?
8
- query = ScimRails::ScimQueryParser.new(
9
- params[:filter], ScimRails.config.queryable_user_attributes
10
- )
11
-
12
- users = @company
13
- .public_send(ScimRails.config.scim_users_scope)
14
- .where(
15
- "#{ScimRails.config.scim_users_model
16
- .connection.quote_column_name(query.attribute)} #{query.operator} ?",
17
- query.parameter
18
- )
19
- .order(ScimRails.config.scim_users_list_order)
20
- else
21
- users = @company
22
- .public_send(ScimRails.config.scim_users_scope)
23
- .order(ScimRails.config.scim_users_list_order)
24
- end
25
-
26
- counts = ScimCount.new(
27
- start_index: params[:startIndex],
28
- limit: params[:count],
29
- total: users.count
30
- )
31
-
32
- json_scim_response(object: users, counts: counts)
33
- end
34
-
35
- def create
36
- if ScimRails.config.scim_user_prevent_update_on_create
37
- user = @company
38
- .public_send(ScimRails.config.scim_users_scope)
39
- .create!(permitted_user_params)
40
- else
41
- username_key = ScimRails.config.queryable_user_attributes[:userName]
42
- find_by_username = {}
43
- find_by_username[username_key] = permitted_user_params[username_key]
44
- user = @company
45
- .public_send(ScimRails.config.scim_users_scope)
46
- .find_or_create_by(find_by_username)
47
- user.update!(permitted_user_params)
48
- end
49
- json_scim_response(object: user, status: :created)
50
- end
51
-
52
- def show
53
- user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
54
- json_scim_response(object: user)
55
- end
56
-
57
- def put_update
58
- user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
59
- user.update!(permitted_user_params)
60
- json_scim_response(object: user)
61
- end
62
-
63
- def patch_update
64
- user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
65
- patch = ScimPatch.new(params, :user)
66
- patch.save(user)
67
-
68
- json_scim_response(object: user)
69
- end
70
-
71
- def destroy
72
- unless ScimRails.config.user_destroy_method
73
- raise ScimRails::ExceptionHandler::InvalidConfiguration
74
- end
75
-
76
- user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
77
- raise ActiveRecord::RecordNotFound unless user
78
-
79
- begin
80
- user.public_send(ScimRails.config.user_destroy_method)
81
- rescue NoMethodError => e
82
- raise ScimRails::ExceptionHandler::InvalidConfiguration, e.message
83
- rescue ActiveRecord::RecordNotDestroyed => e
84
- raise ScimRails::ExceptionHandler::InvalidRequest, e.message
85
- rescue => e
86
- raise ScimRails::ExceptionHandler::UnexpectedError, e.message
87
- end
88
-
89
- head :no_content
90
- end
91
-
92
- private
93
-
94
- def permitted_user_params
95
- ScimRails.config.mutable_user_attributes.each.with_object({}) do |attribute, hash|
96
- hash[attribute] = find_value_for(attribute)
97
- end
98
- end
99
-
100
- def controller_schema
101
- ScimRails.config.mutable_user_attributes_schema
102
- end
103
- end
104
- end
@@ -1,40 +0,0 @@
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
@@ -1,49 +0,0 @@
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.gsub(/\[(.+?)\]/, ".0").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
- dig_keys = attribute.split(".").map do |step|
17
- step == "0" ? 0 : step.to_sym
18
- end
19
-
20
- mapped_attribute = query_attributes.dig(*dig_keys)
21
- raise ScimRails::ExceptionHandler::InvalidQuery if mapped_attribute.blank?
22
-
23
- mapped_attribute
24
- end
25
-
26
- def operator
27
- sql_comparison_operator(query_elements[1])
28
- end
29
-
30
- def parameter
31
- parameter = query_elements[2..-1].join(" ")
32
- return if parameter.blank?
33
-
34
- parameter.gsub(/"/, "")
35
- end
36
-
37
- private
38
-
39
- def sql_comparison_operator(element)
40
- case element
41
- when "eq"
42
- "="
43
- else
44
- # TODO: implement additional query filters
45
- raise ScimRails::ExceptionHandler::InvalidQuery
46
- end
47
- end
48
- end
49
- end
@@ -1,8 +0,0 @@
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
@@ -1,7 +0,0 @@
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
@@ -1,25 +0,0 @@
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
@@ -1,12 +0,0 @@
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
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ScimRails
4
- VERSION = '0.9.0'
5
- end
data/lib/scim_rails.rb DELETED
@@ -1,6 +0,0 @@
1
- require "scim_rails/engine"
2
- require "scim_rails/config"
3
- require "scim_rails/encoder"
4
-
5
- module ScimRails
6
- end
@@ -1,62 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe ScimRails::Encoder do
4
- let(:company) { Company.new(subdomain: "test") }
5
-
6
- describe "::encode" do
7
- context "with signing configuration" do
8
- it "generates a signed token with the company attribute" do
9
- token = ScimRails::Encoder.encode(company)
10
- payload = ScimRails::Encoder.decode(token)
11
-
12
- expect(token).to match /[a-z|A-Z|0-9.]{16,}\.[a-z|A-Z|0-9.]{16,}/
13
- expect(payload).to contain_exactly(["iat", Integer], ["subdomain", "test"])
14
- end
15
- end
16
-
17
- context "without signing configuration" do
18
- before do
19
- allow(ScimRails.config).to receive(:signing_secret).and_return(nil)
20
- allow(ScimRails.config).to receive(:signing_algorithm).and_return(ScimRails::Config::ALGO_NONE)
21
- end
22
-
23
- it "generates an unsigned token with the company attribute" do
24
- token = ScimRails::Encoder.encode(company)
25
- payload = ScimRails::Encoder.decode(token)
26
-
27
- expect(token).to match /[a-z|A-Z|0-9.]{16,}/
28
- expect(payload).to contain_exactly(["iat", Integer], ["subdomain", "test"])
29
- end
30
- end
31
- end
32
-
33
- describe "::decode" do
34
- let(:token) { ScimRails::Encoder.encode(company) }
35
-
36
- it "raises InvalidCredentials error for an invalid token" do
37
- token = "f487bf84bfub4f74fj4894fnh483f4h4u8f"
38
- expect { ScimRails::Encoder.decode(token) }.to raise_error ScimRails::ExceptionHandler::InvalidCredentials
39
- end
40
-
41
- context "with signing configuration" do
42
- it "decodes a signed token, returning the company attributes" do
43
- payload = ScimRails::Encoder.decode(token)
44
-
45
- expect(payload).to contain_exactly(["iat", Integer], ["subdomain", "test"])
46
- end
47
- end
48
-
49
- context "without signing configuration" do
50
- before do
51
- allow(ScimRails.config).to receive(:signing_secret).and_return(nil)
52
- allow(ScimRails.config).to receive(:signing_algorithm).and_return(ScimRails::Config::ALGO_NONE)
53
- end
54
-
55
- it "decodes an unsigned token, returning the company attributes" do
56
- payload = ScimRails::Encoder.decode(token)
57
-
58
- expect(payload).to contain_exactly(["iat", Integer], ["subdomain", "test"])
59
- end
60
- end
61
- end
62
- end