rhino_project_core 0.20.0.beta.18

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