rhino_project_core 0.20.0.beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/stripe_flow.png +0 -0
  5. data/app/controllers/concerns/rhino/authenticated.rb +18 -0
  6. data/app/controllers/concerns/rhino/error_handling.rb +58 -0
  7. data/app/controllers/concerns/rhino/paper_trail_whodunnit.rb +11 -0
  8. data/app/controllers/concerns/rhino/permit.rb +38 -0
  9. data/app/controllers/concerns/rhino/set_current_user.rb +13 -0
  10. data/app/controllers/rhino/account_controller.rb +34 -0
  11. data/app/controllers/rhino/active_model_extension_controller.rb +52 -0
  12. data/app/controllers/rhino/base_controller.rb +23 -0
  13. data/app/controllers/rhino/crud_controller.rb +57 -0
  14. data/app/controllers/rhino/simple_controller.rb +11 -0
  15. data/app/controllers/rhino/simple_stream_controller.rb +12 -0
  16. data/app/helpers/rhino/omniauth_helper.rb +67 -0
  17. data/app/helpers/rhino/policy_helper.rb +42 -0
  18. data/app/helpers/rhino/segment_helper.rb +62 -0
  19. data/app/models/rhino/account.rb +13 -0
  20. data/app/models/rhino/current.rb +7 -0
  21. data/app/models/rhino/user.rb +44 -0
  22. data/app/overrides/active_record/autosave_association_override.rb +18 -0
  23. data/app/overrides/active_record/delegated_type_override.rb +14 -0
  24. data/app/overrides/activestorage/direct_uploads_controller_override.rb +23 -0
  25. data/app/overrides/activestorage/redirect_controller_override.rb +21 -0
  26. data/app/overrides/activestorage/redirect_representation_controller_override.rb +21 -0
  27. data/app/overrides/devise_token_auth/confirmations_controller_override.rb +14 -0
  28. data/app/overrides/devise_token_auth/omniauth_callbacks_controller_override.rb +45 -0
  29. data/app/overrides/devise_token_auth/passwords_controller_override.rb +9 -0
  30. data/app/overrides/devise_token_auth/registrations_controller_override.rb +20 -0
  31. data/app/overrides/devise_token_auth/sessions_controller_override.rb +26 -0
  32. data/app/overrides/devise_token_auth/token_validations_controller_override.rb +18 -0
  33. data/app/policies/rhino/account_policy.rb +27 -0
  34. data/app/policies/rhino/active_storage_attachment_policy.rb +16 -0
  35. data/app/policies/rhino/admin_policy.rb +20 -0
  36. data/app/policies/rhino/base_policy.rb +72 -0
  37. data/app/policies/rhino/crud_policy.rb +109 -0
  38. data/app/policies/rhino/editor_policy.rb +12 -0
  39. data/app/policies/rhino/global_policy.rb +8 -0
  40. data/app/policies/rhino/resource_info_policy.rb +9 -0
  41. data/app/policies/rhino/user_policy.rb +20 -0
  42. data/app/policies/rhino/viewer_policy.rb +19 -0
  43. data/app/resources/rhino/info_graph.rb +41 -0
  44. data/app/resources/rhino/open_api_info.rb +108 -0
  45. data/config/routes.rb +19 -0
  46. data/db/migrate/20180101000000_devise_token_auth_create_users.rb +54 -0
  47. data/db/migrate/20180622142754_add_allow_change_password_to_users.rb +5 -0
  48. data/db/migrate/20191217010224_add_approved_to_users.rb +7 -0
  49. data/db/migrate/20200503182019_change_tokens_to_json_b.rb +9 -0
  50. data/lib/commands/rhino_command.rb +59 -0
  51. data/lib/generators/rhino/dev/setup/setup_generator.rb +175 -0
  52. data/lib/generators/rhino/dev/setup/templates/env.client.tt +4 -0
  53. data/lib/generators/rhino/dev/setup/templates/env.server.tt +35 -0
  54. data/lib/generators/rhino/dev/setup/templates/prepare-commit-msg +17 -0
  55. data/lib/generators/rhino/install/install_generator.rb +24 -0
  56. data/lib/generators/rhino/install/templates/account.rb +4 -0
  57. data/lib/generators/rhino/install/templates/rhino.rb +24 -0
  58. data/lib/generators/rhino/install/templates/user.rb +4 -0
  59. data/lib/generators/rhino/module/module_generator.rb +93 -0
  60. data/lib/generators/rhino/module/templates/engine.rb.tt +19 -0
  61. data/lib/generators/rhino/module/templates/install_generator.rb.tt +12 -0
  62. data/lib/generators/rhino/module/templates/module_tasks.rake.tt +17 -0
  63. data/lib/generators/rhino/policy/policy_generator.rb +33 -0
  64. data/lib/generators/rhino/policy/templates/policy.rb.tt +46 -0
  65. data/lib/generators/test_unit/rhino_policy_generator.rb +13 -0
  66. data/lib/generators/test_unit/templates/policy_test.rb.tt +57 -0
  67. data/lib/rhino/engine.rb +140 -0
  68. data/lib/rhino/omniauth/strategies/azure_o_auth2.rb +16 -0
  69. data/lib/rhino/resource/active_model_extension/backing_store/google_sheet.rb +89 -0
  70. data/lib/rhino/resource/active_model_extension/backing_store.rb +33 -0
  71. data/lib/rhino/resource/active_model_extension/describe.rb +38 -0
  72. data/lib/rhino/resource/active_model_extension/params.rb +70 -0
  73. data/lib/rhino/resource/active_model_extension/properties.rb +229 -0
  74. data/lib/rhino/resource/active_model_extension/reference.rb +50 -0
  75. data/lib/rhino/resource/active_model_extension/routing.rb +15 -0
  76. data/lib/rhino/resource/active_model_extension/serialization.rb +16 -0
  77. data/lib/rhino/resource/active_model_extension.rb +38 -0
  78. data/lib/rhino/resource/active_record_extension/describe.rb +44 -0
  79. data/lib/rhino/resource/active_record_extension/params.rb +213 -0
  80. data/lib/rhino/resource/active_record_extension/properties.rb +85 -0
  81. data/lib/rhino/resource/active_record_extension/properties_describe.rb +226 -0
  82. data/lib/rhino/resource/active_record_extension/reference.rb +50 -0
  83. data/lib/rhino/resource/active_record_extension/routing.rb +21 -0
  84. data/lib/rhino/resource/active_record_extension/search.rb +23 -0
  85. data/lib/rhino/resource/active_record_extension/serialization.rb +16 -0
  86. data/lib/rhino/resource/active_record_extension.rb +30 -0
  87. data/lib/rhino/resource/active_record_tree.rb +50 -0
  88. data/lib/rhino/resource/active_storage_extension.rb +41 -0
  89. data/lib/rhino/resource/describe.rb +19 -0
  90. data/lib/rhino/resource/owner.rb +172 -0
  91. data/lib/rhino/resource/params.rb +31 -0
  92. data/lib/rhino/resource/properties.rb +192 -0
  93. data/lib/rhino/resource/reference.rb +31 -0
  94. data/lib/rhino/resource/routing.rb +107 -0
  95. data/lib/rhino/resource/serialization.rb +13 -0
  96. data/lib/rhino/resource/sieves.rb +36 -0
  97. data/lib/rhino/resource.rb +54 -0
  98. data/lib/rhino/sieve/filter.rb +149 -0
  99. data/lib/rhino/sieve/helpers.rb +11 -0
  100. data/lib/rhino/sieve/limit.rb +20 -0
  101. data/lib/rhino/sieve/offset.rb +16 -0
  102. data/lib/rhino/sieve/order.rb +143 -0
  103. data/lib/rhino/sieve/search.rb +20 -0
  104. data/lib/rhino/sieve.rb +158 -0
  105. data/lib/rhino/test_case/controller.rb +134 -0
  106. data/lib/rhino/test_case/override.rb +19 -0
  107. data/lib/rhino/test_case/policy.rb +76 -0
  108. data/lib/rhino/test_case.rb +10 -0
  109. data/lib/rhino/version.rb +17 -0
  110. data/lib/rhino.rb +129 -0
  111. data/lib/tasks/rhino.rake +38 -0
  112. data/lib/tasks/rhino_dev.rake +17 -0
  113. data/lib/validators/country_validator.rb +11 -0
  114. data/lib/validators/email_validator.rb +8 -0
  115. data/lib/validators/ipv4_validator.rb +10 -0
  116. data/lib/validators/mac_address_validator.rb +9 -0
  117. metadata +178 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f8779f64179f5c2a26ea37d704c7ac7355f713fc2f386aa6ab08e3e7a0ff174f
4
+ data.tar.gz: 2e6139b4b62758822ab9db651b9468af7726e0a56d0a34c1d5712b418e21db70
5
+ SHA512:
6
+ metadata.gz: 877bcedc2608beaa6d9d667991858d1b3c272ff876380e1ec1c34e07504cfbc79b153f686aafecc47b8f233d7939c8310012ec99c9f160ff30bc36543d079efb
7
+ data.tar.gz: 3a63eab50a46bbfc236d1cc8d4e0f123bf908ae2a94fbaaf2e251c00817daff159c4acc0a4400730e7cb2df0c39b347595cbac6f810e7aa81c74bfa228d74a56
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Rhino
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'rhino'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install rhino
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ task :package
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Rhino'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task default: :test
Binary file
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module Authenticated
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ prepend_before_action :authenticate_user!
9
+ end
10
+
11
+ # Overrides the default devise_token_auth handler
12
+ def render_authenticate_error
13
+ cookies.delete(DeviseTokenAuth.cookie_name, domain: DeviseTokenAuth.cookie_attributes[:domain])
14
+
15
+ super
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module ErrorHandling
5
+ extend ActiveSupport::Concern
6
+
7
+ included do # rubocop:disable Metrics/BlockLength
8
+ rescue_from Exception, with: :handle_uncaught_error
9
+
10
+ # ActiveRecord::DeleteRestrictionError is for dependent: :restrict_with_exception
11
+ rescue_from ActionController::ParameterMissing, ActiveRecord::DeleteRestrictionError do |e|
12
+ render json: { errors: [e.message] }, status: :bad_request
13
+ end
14
+
15
+ rescue_from Pundit::NotAuthorizedError, with: :forbidden
16
+
17
+ # ActiveRecord::RecordNotDestroyed is for dependent: :restrict_with_error
18
+ rescue_from ActiveRecord::RecordInvalid, ActiveRecord::RecordNotDestroyed do |e|
19
+ unprocessable e.record.errors
20
+ end
21
+
22
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
23
+
24
+ def handle_uncaught_error(exception)
25
+ # Send to rollbar if available
26
+ Rollbar.error(exception) if defined? Rollbar
27
+
28
+ logger.error("Internal server error#{exception.class} #{exception.message} #{exception.backtrace.join("\n")}")
29
+
30
+ render json: { errors: ['Internal server error.'] },
31
+ status: :internal_server_error
32
+ end
33
+
34
+ def not_found
35
+ render json: { errors: ['Not found.'] }, status: :not_found
36
+ end
37
+
38
+ def bad_request(errors = nil)
39
+ render json: { errors: (errors || ['Bad request.']) },
40
+ status: :bad_request
41
+ end
42
+
43
+ def forbidden
44
+ render json: { errors: ['Access denied.'] }, status: :forbidden
45
+ end
46
+
47
+ def unprocessable(errors = nil)
48
+ render json: {
49
+ errors: (errors&.to_hash(full_messages: true) || ['Unprocessable request.'])
50
+ }, status: :unprocessable_entity
51
+ end
52
+
53
+ def cors
54
+ render json: {}
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module PaperTrailWhodunnit
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :set_paper_trail_whodunnit if defined? PaperTrail
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module Permit
5
+ extend ActiveSupport::Concern
6
+
7
+ protected
8
+ # The params are wrapped with 'crud' if the crud_controller is called directly
9
+ # instead of via a customization class
10
+ #
11
+ # Protected to prevent polluting the action method
12
+ #
13
+ # FIXME: Couldn't find a way to call wrap_parameters appropriately
14
+ # FIXME: Will some sort of namespacing break this?
15
+ # _wrapper_options is semi-private but we can get at it
16
+ def pundit_params_for(_record)
17
+ params.require(_wrapper_options.name)
18
+ end
19
+
20
+ private
21
+ def permit_and_transform(model = @model)
22
+ klass.transform_params(permitted_attributes(model))
23
+ end
24
+
25
+ def permit_model(model = @model)
26
+ model_policy = Pundit.policy(current_user, model)
27
+
28
+ model_params = ActionController::Parameters.new(model.to_caching_json)
29
+ model_params[:can_current_user_edit] = model_policy.update?
30
+ model_params[:can_current_user_destroy] = model_policy.destroy?
31
+ model_params.permit(model_policy.permitted_attributes_for_show + %i[can_current_user_edit can_current_user_destroy])
32
+ end
33
+
34
+ def permit_and_render
35
+ render json: permit_model
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module SetCurrentUser
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action do
9
+ Current.user = current_user
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class AccountController < BaseController
5
+ include Rhino::Authenticated
6
+ include Rhino::Permit
7
+
8
+ # Confirm we are calling authorize and scope correctly
9
+ after_action :verify_authorized
10
+ after_action :verify_policy_scoped
11
+
12
+ def show
13
+ @model = authorize_resource
14
+
15
+ permit_and_render
16
+ end
17
+
18
+ def update
19
+ @model = authorize_resource
20
+ @model.update!(permit_and_transform)
21
+
22
+ permit_and_render
23
+ end
24
+
25
+ private
26
+ def find_resource
27
+ policy_scope(klass).first
28
+ end
29
+
30
+ def authorize_resource
31
+ authorize find_resource
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class ActiveModelExtensionController < BaseController
5
+ include Rhino::Authenticated
6
+ include Rhino::Permit
7
+
8
+ # Confirm we are calling authorize and scope correctly
9
+ after_action :verify_authorized
10
+ # after_action :verify_policy_scoped, only: %i[index show]
11
+
12
+ def create
13
+ @model = authorize klass.new(permit_and_transform(klass.new))
14
+ klass.backing_store_create(@model)
15
+
16
+ permit_and_render
17
+ end
18
+
19
+ def index
20
+ authorize klass
21
+ @models = klass.backing_store_index
22
+
23
+ # FIXME: - policy and sieve scopings
24
+ # @models = klass.sieves.resolve(policy_scope(klass), params)
25
+ render json: {
26
+ results: @models.map { |m| permit_model(m) },
27
+ total: @models.count
28
+ }
29
+ end
30
+
31
+ def show
32
+ @model = authorize(klass.backing_store_show(params[:id]))
33
+
34
+ permit_and_render
35
+ end
36
+
37
+ def update
38
+ @model = authorize klass.new(permit_and_transform(klass.new))
39
+ # FIXME: Return updated model
40
+ @model.backing_store_update
41
+
42
+ permit_and_render
43
+ end
44
+
45
+ def destroy
46
+ @model = authorize klass.backing_store_show(params[:id])
47
+ @model.backing_store_destroy
48
+
49
+ permit_and_render
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class BaseController < ActionController::API
5
+ include ActionController::Cookies
6
+ include DeviseTokenAuth::Concerns::SetUserByToken
7
+ include Pundit
8
+
9
+ include Rhino::ErrorHandling
10
+ include Rhino::SetCurrentUser
11
+ include Rhino::PaperTrailWhodunnit
12
+
13
+ # https://api.rubyonrails.org/classes/AbstractController/Base.html#method-c-abstract-21
14
+ # Prevents the utility methods below from showing up as actions in CrudController
15
+ abstract!
16
+
17
+ respond_to :json
18
+
19
+ def klass
20
+ @klass ||= params.delete(:rhino_resource).constantize
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class CrudController < BaseController
5
+ include Rhino::Authenticated
6
+ include Rhino::Permit
7
+
8
+ # Confirm we are calling authorize and scope correctly
9
+ after_action :verify_authorized
10
+ after_action :verify_policy_scoped, only: %i[index show]
11
+
12
+ wrap_parameters :crud, format: [:json]
13
+
14
+ def create
15
+ @model = authorize klass.new(permit_and_transform(klass))
16
+ @model.save!
17
+
18
+ permit_and_render
19
+ end
20
+
21
+ def index
22
+ authorize klass
23
+
24
+ @models = klass.sieves.resolve(policy_scope(klass), params)
25
+ render json: {
26
+ results: @models.eager_load_refs.map { |m| permit_model(m) },
27
+ total: @models.unscope(:limit, :offset).reselect(:id).count
28
+ }
29
+ end
30
+
31
+ def show
32
+ @model = authorize find_resource(policy_scope(klass).eager_load_refs)
33
+
34
+ permit_and_render
35
+ end
36
+
37
+ def update
38
+ @model = authorize find_resource
39
+ @model.update!(permit_and_transform)
40
+
41
+ permit_and_render
42
+ end
43
+
44
+ def destroy
45
+ @model = authorize find_resource
46
+ @model.destroy!
47
+
48
+ permit_and_render
49
+ end
50
+
51
+ private
52
+ def find_resource(scope = klass.all)
53
+ scope = scope.friendly if scope.respond_to? :friendly
54
+ scope.find(params[:id])
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class SimpleController < BaseController
5
+ def action_missing(action)
6
+ authorize klass, "#{action}?".to_sym
7
+
8
+ render json: klass.send(action)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class SimpleStreamController < BaseController
5
+ def action_missing(action)
6
+ authorize klass, "#{action}?".to_sym
7
+
8
+ info = klass.send(action)
9
+ send_file(info[:file], info.except(:file)) if info.key?(:file)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module OmniauthHelper
5
+ module_function
6
+
7
+ def strategies_metadata
8
+ params = { resource_class: "User" }
9
+
10
+ strategies.each_with_object([]) do |strategy, array|
11
+ array << {
12
+ name: strategy,
13
+ path: "#{::OmniAuth.config.path_prefix}/#{strategy}?#{params.to_param}"
14
+ }
15
+ end
16
+ end
17
+
18
+ def strategies
19
+ strategies = ENV.keys.filter_map do |env|
20
+ match = /AUTH_(.*)_CLIENT_ID/.match(env)
21
+ next unless match
22
+
23
+ match[1].downcase.to_sym
24
+ end.uniq
25
+
26
+ strategies += [:developer] if Rails.env.development? && !Rake.try(:application)
27
+
28
+ strategies
29
+ end
30
+
31
+ def app_info(strategy)
32
+ case strategy
33
+ when :developer
34
+ []
35
+ when :azure_oauth2
36
+ azure_info
37
+ when :auth0
38
+ auth0_info
39
+ else
40
+ env_keys(strategy)
41
+ end
42
+ end
43
+
44
+ def env_keys(strategy)
45
+ ups = strategy.to_s.upcase
46
+ [ENV["AUTH_#{ups}_CLIENT_ID"], ENV["AUTH_#{ups}_SECRET_KEY"]]
47
+ end
48
+
49
+ def azure_info
50
+ [
51
+ client_id: ENV["AUTH_AZURE_OAUTH2_CLIENT_ID"],
52
+ client_secret: ENV["AUTH_AZURE_OAUTH2_SECRET_KEY"],
53
+ tenant_id: ENV["AUTH_AZURE_OAUTH2_TENANT_ID"]
54
+ ]
55
+ end
56
+
57
+ def auth0_info
58
+ [client_id: ENV["AUTH_AUTH0_CLIENT_ID"],
59
+ client_secret: ENV["AUTH_AUTH0_SECRET_KEY"],
60
+ domain: ENV["AUTH_AUTH0_DOMAIN"],
61
+ callback_path: "/api/auth/omniauth/auth0/callback",
62
+ authorize_params: {
63
+ scope: "openid profile"
64
+ }]
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module PolicyHelper
5
+ module_function
6
+
7
+ # Looks for the policy associated with a role and resource
8
+ #
9
+ # If a policy for a role and resource is not found, looks for the policy
10
+ # associated with the role.
11
+ #
12
+ # === Examples
13
+ #
14
+ # find_policy(:author, Blog)
15
+ #
16
+ def find_policy(role, resource, additional_class = nil)
17
+ role = role.to_s if role.is_a? Symbol
18
+ role = role.classify
19
+
20
+ resource = resource.class if resource.class != Class
21
+ resource = resource.to_s.classify
22
+
23
+ policy_class = 'Policy'
24
+ policy_class = "#{policy_class}::#{additional_class}" if additional_class
25
+
26
+ # Look for role and resource specific policy
27
+ policy_constant = "#{role}#{resource}#{policy_class}".safe_constantize
28
+ return policy_constant if policy_constant.present?
29
+
30
+ # Fall back to just the role specific policy
31
+ policy_constant = "#{role}#{policy_class}".safe_constantize
32
+ return policy_constant if policy_constant.present?
33
+
34
+ # Fall back just to the rhino version
35
+ "Rhino::#{role}#{policy_class}".safe_constantize
36
+ end
37
+
38
+ def find_policy_scope(role, resource)
39
+ find_policy(role, resource, 'Scope')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ module SegmentHelper
5
+ module_function
6
+
7
+ def track(type, user)
8
+ segment_track(type, user)
9
+ rescue StandardError => e
10
+ Rollbar.error(e)
11
+ end
12
+
13
+ def track_account(type, organization, users_role = nil)
14
+ segment_track_account(type, organization, users_role)
15
+ rescue StandardError => e
16
+ Rollbar.error(e)
17
+ end
18
+
19
+ def track_invite(users_role_invite)
20
+ segment_track_invite(users_role_invite)
21
+ rescue StandardError => e
22
+ Rollbar.error(e)
23
+ end
24
+
25
+ def segment_track(type, user)
26
+ Analytics.track(
27
+ user_id: user.id,
28
+ event: type,
29
+ properties: {
30
+ name: user.name,
31
+ email: user.email,
32
+ current_sign_in_ip: user.current_sign_in_ip,
33
+ last_sign_in_ip: user.last_sign_in_ip,
34
+ approved: user.approved
35
+ }
36
+ )
37
+ end
38
+
39
+ def segment_track_account(type, organization, users_role)
40
+ properties = {}
41
+ properties[:account_name] = organization.name
42
+ properties[:user_email] = users_role.user&.email unless users_role.nil?
43
+ properties[:role] = users_role.role&.name unless users_role.nil?
44
+ Analytics.track(
45
+ user_id: users_role.nil? ? organization.id : users_role.user&.id,
46
+ event: type,
47
+ properties:
48
+ )
49
+ end
50
+
51
+ def segment_track_invite(users_role_invite)
52
+ Analytics.track(
53
+ event: "Invite Sent",
54
+ user_id: Rhino::Current.user&.id,
55
+ properties: {
56
+ invitee_email: users_role_invite.email,
57
+ invitee_role: users_role_invite.role&.name
58
+ }
59
+ )
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class Account < User
5
+ self.table_name = "users"
6
+
7
+ rhino_owner_global
8
+
9
+ rhino_routing only: %i[show update], singular: true
10
+ rhino_controller :account
11
+ rhino_policy :account
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class Current < ActiveSupport::CurrentAttributes
5
+ attribute :user
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rhino
4
+ class User < ApplicationRecord
5
+ self.abstract_class = true
6
+
7
+ include DeviseTokenAuth::Concerns::User
8
+
9
+ rhino_properties_read only: %i[id name nickname email image]
10
+ rhino_properties_create only: %i[name nickname email]
11
+ rhino_properties_update only: %i[name nickname]
12
+
13
+ rhino_policy :user
14
+
15
+ def self.devise_modules_to_load
16
+ modules = %i[database_authenticatable registerable recoverable rememberable trackable validatable confirmable omniauthable]
17
+ modules.push :invitable if Rhino.resources.include?("Organization")
18
+ end
19
+ devise(*devise_modules_to_load)
20
+
21
+ validates :email, uniqueness: { case_sensitive: false }
22
+ after_create_commit :track_sign_up
23
+
24
+ def display_name
25
+ name || email
26
+ end
27
+
28
+ def self.roles_for_auth(auth_owner, record = nil)
29
+ return {} unless auth_owner
30
+
31
+ # If user is logged in, but no record, they are still an admin for their data
32
+ # Otherwise owner must match to be an admin
33
+ # A list of roles as hash keys with an array of base_owners for each
34
+ return { admin: [auth_owner] } if !record.respond_to?(:base_owner_ids) || record.base_owner_ids.include?(auth_owner&.id)
35
+
36
+ {}
37
+ end
38
+
39
+ private
40
+ def track_sign_up
41
+ Rhino::SegmentHelper.track("Signed Up", self)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # correction for nested forms errors indexation
4
+ # this correction is still necessary in rails 7.0
5
+ module ActiveRecord::AutosaveAssociation
6
+ private
7
+ def validate_collection_association(reflection)
8
+ return unless (association = association_instance_get(reflection.name))
9
+
10
+ return unless (records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]))
11
+
12
+ all_records = association.target.find_all
13
+ records.each do |record|
14
+ index = all_records.find_index(record)
15
+ association_valid?(reflection, record, index)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # correction for nested forms errors indexation
4
+ # this correction is still necessary in rails 7.0
5
+ module ActiveRecord::DelegatedType
6
+ def delegated_type(role, types:, **options)
7
+ belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
8
+ define_delegated_type_methods(role, types:, options:)
9
+
10
+ define_singleton_method "#{role}_types" do
11
+ types.map(&:to_s)
12
+ end
13
+ end
14
+ end