action_policy 0.7.4 → 0.7.6

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -3
  3. data/lib/.rbnext/3.0/action_policy/behaviours/policy_for.rb +1 -1
  4. data/lib/.rbnext/3.0/action_policy/policy/cache.rb +1 -1
  5. data/lib/.rbnext/3.0/action_policy/policy/execution_result.rb +1 -1
  6. data/lib/.rbnext/3.0/action_policy/policy/pre_check.rb +3 -3
  7. data/lib/.rbnext/3.0/action_policy/rspec/be_authorized_to.rb +4 -4
  8. data/lib/.rbnext/3.0/action_policy/rspec/have_authorized_scope.rb +4 -4
  9. data/lib/.rbnext/3.0/action_policy/utils/pretty_print.rb +2 -2
  10. data/lib/.rbnext/3.1/action_policy/behaviours/policy_for.rb +1 -1
  11. data/lib/.rbnext/3.2/action_policy/behaviours/policy_for.rb +1 -1
  12. data/lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb +4 -4
  13. data/lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb +4 -4
  14. data/lib/.rbnext/3.4/action_policy/behaviours/policy_for.rb +70 -0
  15. data/lib/.rbnext/3.4/action_policy/i18n.rb +56 -0
  16. data/lib/.rbnext/3.4/action_policy/policy/cache.rb +101 -0
  17. data/lib/.rbnext/3.4/action_policy/policy/pre_check.rb +160 -0
  18. data/lib/.rbnext/3.4/action_policy/rspec/be_authorized_to.rb +96 -0
  19. data/lib/.rbnext/3.4/action_policy/rspec/have_authorized_scope.rb +130 -0
  20. data/lib/.rbnext/3.4/action_policy/utils/pretty_print.rb +155 -0
  21. data/lib/action_policy/behaviour.rb +1 -1
  22. data/lib/action_policy/behaviours/policy_for.rb +1 -1
  23. data/lib/action_policy/i18n.rb +1 -1
  24. data/lib/action_policy/policy/cache.rb +1 -1
  25. data/lib/action_policy/policy/execution_result.rb +1 -1
  26. data/lib/action_policy/policy/pre_check.rb +3 -3
  27. data/lib/action_policy/rails/controller.rb +45 -4
  28. data/lib/action_policy/rails/policy/instrumentation.rb +1 -0
  29. data/lib/action_policy/rspec/be_authorized_to.rb +3 -3
  30. data/lib/action_policy/rspec/have_authorized_scope.rb +3 -3
  31. data/lib/action_policy/test_helper.rb +1 -1
  32. data/lib/action_policy/utils/pretty_print.rb +2 -2
  33. data/lib/action_policy/version.rb +1 -1
  34. data/lib/action_policy.rb +1 -1
  35. data/lib/ruby_lsp/action_policy/addon.rb +170 -0
  36. metadata +11 -6
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ # From https://gist.github.com/skryukov/35539d57b51f38235faaace2c1a2c1a1
4
+
5
+ require "active_support/inflector"
6
+
7
+ module RubyLsp
8
+ module ActionPolicy
9
+ class Addon < ::RubyLsp::Addon
10
+ def name
11
+ "ActionPolicy"
12
+ end
13
+
14
+ def activate(global_state, outgoing_queue)
15
+ require "action_policy"
16
+ warn "[ActionPolicy] Activating Ruby LSP addon v#{::ActionPolicy::VERSION}"
17
+ end
18
+
19
+ def deactivate
20
+ end
21
+
22
+ def create_definition_listener(response_builder, node_context, uri, dispatcher)
23
+ Definition.new(response_builder, node_context, uri, dispatcher)
24
+ end
25
+ end
26
+
27
+ class Definition
28
+ include Requests::Support::Common
29
+ include ActiveSupport::Inflector
30
+
31
+ POLICY_SUPERCLASSES = ["ApplicationPolicy", "ActionPolicy::Base"].freeze
32
+
33
+ def initialize(response_builder, uri, node_context, dispatcher)
34
+ @response_builder = response_builder
35
+ @node_context = node_context
36
+ @uri = uri
37
+ @path = uri.to_standardized_path
38
+ @policy_rules_cache = {}
39
+
40
+ dispatcher.register(self, :on_symbol_node_enter)
41
+ end
42
+
43
+ def on_symbol_node_enter(node)
44
+ return unless in_authorize_call?
45
+
46
+ target = @node_context.call_node
47
+ # authorization target is the first argument (if explicit)
48
+ policy_class = find_policy_class(target.arguments&.child_node&.first)
49
+ return unless policy_class
50
+
51
+ policy_path = find_policy_file(policy_class)
52
+ return unless policy_path
53
+
54
+ ensure_policy_rules_cached(policy_path)
55
+ add_definition(policy_path, node.value)
56
+ end
57
+
58
+ private
59
+
60
+ def in_authorize_call?
61
+ call = @node_context.call_node
62
+ call.is_a?(Prism::CallNode) && call.message == "authorize!"
63
+ end
64
+
65
+ def find_policy_class(target)
66
+ content = File.read(@path)
67
+ document = Prism.parse(content)
68
+ class_node = find_containing_class(document.value)
69
+ return derive_policy_from_target(target) unless class_node
70
+
71
+ class_name = class_node.constant_path.slice
72
+ return unless class_name.end_with?("Controller", "Channel")
73
+
74
+ resource_name = class_name
75
+ .delete_suffix("Controller")
76
+ .delete_suffix("Channel")
77
+ .singularize
78
+ "#{resource_name}Policy"
79
+ end
80
+
81
+ def find_containing_class(root)
82
+ return unless root.respond_to?(:statements)
83
+
84
+ root.statements.body.find do |node|
85
+ node.is_a?(Prism::ClassNode) &&
86
+ node.constant_path.slice.end_with?("Controller", "Channel")
87
+ end
88
+ end
89
+
90
+ def derive_policy_from_target(target)
91
+ target_name = case target
92
+ when Prism::InstanceVariableReadNode
93
+ target.name.to_s[1..].classify
94
+ when Prism::ConstantReadNode
95
+ target.name.to_s
96
+ when NilClass
97
+ return
98
+ else
99
+ target.slice
100
+ end
101
+
102
+ target_name.end_with?("Policy") ? target_name : "#{target_name}Policy"
103
+ end
104
+
105
+ def find_policy_file(policy_class)
106
+ file_path = policy_class.gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase
107
+ root_path = Dir.pwd
108
+
109
+ [
110
+ File.join(root_path, "app/policies/#{file_path}.rb"),
111
+ File.join(root_path, "app/policies/#{file_path}_policy.rb"),
112
+ *Dir.glob(File.join(root_path, "app/policies/**/#{file_path}.rb")),
113
+ *Dir.glob(File.join(root_path, "app/policies/**/#{file_path}_policy.rb"))
114
+ ].find { |path| File.exist?(path) }
115
+ end
116
+
117
+ def ensure_policy_rules_cached(policy_path)
118
+ return if @policy_rules_cache[policy_path]
119
+
120
+ content = File.read(policy_path)
121
+ document = Prism.parse(content)
122
+
123
+ document.value.statements.body.each do |stmt|
124
+ if stmt.is_a?(Prism::ClassNode)
125
+ @policy_rules_cache[policy_path] = extract_rules(stmt)
126
+ break
127
+ end
128
+ end
129
+ end
130
+
131
+ def extract_rules(node)
132
+ return {} unless node.body
133
+
134
+ rules = {}
135
+ private_section = false
136
+
137
+ node.body.child_nodes.each do |stmt|
138
+ case stmt
139
+ when Prism::CallNode
140
+ if stmt.message == "private"
141
+ private_section = true
142
+ elsif stmt.message == "alias_rule" && stmt.arguments&.arguments
143
+ stmt.arguments.arguments
144
+ .select { |arg| arg.is_a?(Prism::SymbolNode) }
145
+ .each { |arg| rules[arg.value] ||= stmt.location.start_line }
146
+ end
147
+ when Prism::DefNode
148
+ next if private_section
149
+ rules[stmt.name.to_s] = stmt.location.start_line
150
+ end
151
+ end
152
+ rules
153
+ end
154
+
155
+ def add_definition(policy_path, action)
156
+ rules = @policy_rules_cache[policy_path]
157
+ line_number = rules&.[](action.to_s)
158
+ return unless line_number
159
+
160
+ @response_builder << Interface::Location.new(
161
+ uri: URI::Generic.from_path(path: policy_path).to_s,
162
+ range: Interface::Range.new(
163
+ start: Interface::Position.new(line: line_number - 1, character: 0),
164
+ end: Interface::Position.new(line: line_number - 1, character: 0)
165
+ )
166
+ )
167
+ end
168
+ end
169
+ end
170
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-03-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: ruby-next-core
@@ -162,6 +161,13 @@ files:
162
161
  - lib/.rbnext/3.2/action_policy/rspec/be_authorized_to.rb
163
162
  - lib/.rbnext/3.2/action_policy/rspec/have_authorized_scope.rb
164
163
  - lib/.rbnext/3.2/action_policy/utils/suggest_message.rb
164
+ - lib/.rbnext/3.4/action_policy/behaviours/policy_for.rb
165
+ - lib/.rbnext/3.4/action_policy/i18n.rb
166
+ - lib/.rbnext/3.4/action_policy/policy/cache.rb
167
+ - lib/.rbnext/3.4/action_policy/policy/pre_check.rb
168
+ - lib/.rbnext/3.4/action_policy/rspec/be_authorized_to.rb
169
+ - lib/.rbnext/3.4/action_policy/rspec/have_authorized_scope.rb
170
+ - lib/.rbnext/3.4/action_policy/utils/pretty_print.rb
165
171
  - lib/action_policy.rb
166
172
  - lib/action_policy/authorizer.rb
167
173
  - lib/action_policy/base.rb
@@ -218,6 +224,7 @@ files:
218
224
  - lib/generators/rspec/templates/policy_spec.rb.tt
219
225
  - lib/generators/test_unit/policy_generator.rb
220
226
  - lib/generators/test_unit/templates/policy_test.rb.tt
227
+ - lib/ruby_lsp/action_policy/addon.rb
221
228
  homepage: https://github.com/palkan/action_policy
222
229
  licenses:
223
230
  - MIT
@@ -227,7 +234,6 @@ metadata:
227
234
  documentation_uri: https://actionpolicy.evilmartians.io/
228
235
  homepage_uri: https://actionpolicy.evilmartians.io/
229
236
  source_code_uri: http://github.com/palkan/action_policy
230
- post_install_message:
231
237
  rdoc_options: []
232
238
  require_paths:
233
239
  - lib
@@ -242,8 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
242
248
  - !ruby/object:Gem::Version
243
249
  version: '0'
244
250
  requirements: []
245
- rubygems_version: 3.4.19
246
- signing_key:
251
+ rubygems_version: 3.6.9
247
252
  specification_version: 4
248
253
  summary: Authorization framework for Ruby/Rails application
249
254
  test_files: []