controller_policies 1.0.1 → 1.1.1
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.
- checksums.yaml +4 -4
- data/README.md +36 -7
- data/lib/ability.rb +33 -26
- data/lib/controller_policies/enforcement.rb +15 -3
- data/lib/controller_policies/version.rb +1 -1
- data/lib/generators/policy_definition_generator.rb +10 -6
- data/lib/generators/templates/{definitions.rb.tt → modular_definitions.rb.tt} +1 -1
- data/lib/generators/templates/root_definitions.rb.tt +22 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85d80d00d5bb31de269ac2190cd44a77e9d2aa1623aa7d6e00cf86de12565baa
|
4
|
+
data.tar.gz: ec23ef9100a962f17386693889903182f84cbb247c9dc275da54cec789acc445
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ec83f851cc09988e4664c404b2e99b2a8c48e954f3804652fb0477f66545d75b12887261e4b72c265da16dd3c8ce277e5fdcf6d170eea535b7dfe07c53aa2e8
|
7
|
+
data.tar.gz: 6afa4ececa6d5c04164eafc5c66a578290d6ff3edfcffff76260f6bb645450000b2177d33b49a9d3105eb8f6463a0cda3902a7caee3baa4acfa665b0395e9f39
|
data/README.md
CHANGED
@@ -20,6 +20,16 @@ rails g policy_definition my/namespace
|
|
20
20
|
|
21
21
|
This will generate a file: `app/policies/my/namespace/definitions.rb`
|
22
22
|
|
23
|
+
For controllers without a namespace (root-level controllers), you can generate a root-level definitions file:
|
24
|
+
|
25
|
+
```sh
|
26
|
+
rails g policy_definition root
|
27
|
+
# or simply
|
28
|
+
rails g policy_definition
|
29
|
+
```
|
30
|
+
|
31
|
+
This will generate a file: `app/policies/definitions.rb`
|
32
|
+
|
23
33
|
The developer should edit this file and add the policies for the app. **It is important to note that the location of the definitions file should reflect the namespace of the associated controllers.**
|
24
34
|
|
25
35
|
### `actions` key
|
@@ -43,16 +53,35 @@ end
|
|
43
53
|
|
44
54
|
The above definition will have enforced policies on `Base::FeatureApp::UsersController` (all actions), `Base::DataApp::ProductsController` (`index` action) and `Base::SubscriptionController`.
|
45
55
|
|
56
|
+
For root-level controllers (controllers without a namespace), you can define policies directly in the `Policies` module:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
module Policies
|
60
|
+
DEFINITIONS = [
|
61
|
+
{
|
62
|
+
code: 'user_management',
|
63
|
+
name: 'User Management',
|
64
|
+
description: 'Allows user management operations',
|
65
|
+
actions: ['users', 'profiles#show', 'dashboard']
|
66
|
+
}
|
67
|
+
]
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
The above definition will have enforced policies on `UsersController` (all actions), `ProfilesController` (`show` action) and `DashboardController`.
|
72
|
+
|
46
73
|
**Do note that the `actions` array implement *Regular Expression Matching*. That means that if you have multiple controllers with the same name on their parent namespace, the parent will be matched first.** To avoid this problem, simply add the namespace to match the intended child instead.
|
47
74
|
|
48
75
|
```ruby
|
49
|
-
module
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
76
|
+
module Policies
|
77
|
+
module Base
|
78
|
+
DEFINITIONS = {
|
79
|
+
code: 'policy_code',
|
80
|
+
name: 'Policy Name',
|
81
|
+
description: 'I am a policy.',
|
82
|
+
actions: ['feature_app/users', 'another_base/data_app/products#index', 'base/subscriptions']
|
83
|
+
}
|
84
|
+
end
|
56
85
|
end
|
57
86
|
```
|
58
87
|
|
data/lib/ability.rb
CHANGED
@@ -34,36 +34,28 @@ class Ability
|
|
34
34
|
|
35
35
|
# Filter abilities based on namespace.
|
36
36
|
def where(*queries)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
queries.flat_map do |query|
|
38
|
+
case query
|
39
|
+
when String
|
40
|
+
filter_by_string_query(query)
|
41
|
+
when Module, Class
|
42
|
+
all.select { |ability| ability.namespace == query }
|
43
|
+
else
|
44
|
+
[]
|
44
45
|
end
|
45
46
|
end
|
46
|
-
results
|
47
47
|
end
|
48
48
|
|
49
|
-
# Find an ability within a namespace.
|
50
|
-
# def find(query_string)
|
51
|
-
# where(query_string).first
|
52
|
-
# end
|
53
|
-
|
54
49
|
# Match abilities based on a matching string or regex. The matcher is based on the namespace.
|
55
50
|
def match(expression)
|
56
|
-
case expression
|
57
|
-
when
|
58
|
-
|
51
|
+
case expression
|
52
|
+
when String
|
53
|
+
all.select { |ability| ability.namespace.to_s.match?(/#{trim(expression).camelize}/) }
|
54
|
+
when Regexp
|
55
|
+
regex_matcher(expression)
|
59
56
|
end
|
60
57
|
end
|
61
58
|
|
62
|
-
# Find an ability based on a matching string or regex. The matcher is based on the namespace.
|
63
|
-
# def mill(expression)
|
64
|
-
# match(expression).first
|
65
|
-
# end
|
66
|
-
|
67
59
|
# Path to the policy folder.
|
68
60
|
def policy_path
|
69
61
|
@policy_path ||= Rails.root.join('app/policies')
|
@@ -71,6 +63,15 @@ class Ability
|
|
71
63
|
|
72
64
|
private
|
73
65
|
|
66
|
+
def filter_by_string_query(query)
|
67
|
+
if query.empty? || query == '/'
|
68
|
+
all.select { |ability| ability.namespace == Policies }
|
69
|
+
else
|
70
|
+
target_namespace = "Policies::#{trim(query).camelize}"
|
71
|
+
all.select { |ability| ability.namespace.to_s == target_namespace }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
74
75
|
def definitions
|
75
76
|
@definitions ||= begin
|
76
77
|
data = []
|
@@ -82,14 +83,20 @@ class Ability
|
|
82
83
|
end
|
83
84
|
|
84
85
|
def definition_files_post_processing(file_path)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
policy_definition
|
86
|
+
namespace_path = convert_namespace(file_path)
|
87
|
+
module_constant = resolve_module_constant(namespace_path)
|
88
|
+
|
89
|
+
module_constant::DEFINITIONS.map do |policy_definition|
|
90
|
+
policy_definition.merge(namespace: module_constant)
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
94
|
+
def resolve_module_constant(namespace_path)
|
95
|
+
return Policies if namespace_path.empty?
|
96
|
+
|
97
|
+
"Policies::#{namespace_path}".constantize
|
98
|
+
end
|
99
|
+
|
93
100
|
def regex_matcher(expression)
|
94
101
|
all.select do |ability|
|
95
102
|
ability.namespace.to_s.match?(expression) || ability.namespace.to_s.underscore.match?(expression)
|
@@ -34,11 +34,23 @@ module ControllerPolicies
|
|
34
34
|
# Method that attempts to go through policy definitions and check if it matches the current controller.
|
35
35
|
# If it finds a match, it will be an applicable definition to check abilities for.
|
36
36
|
def applicable_abilities
|
37
|
-
@applicable_abilities ||=
|
38
|
-
|
39
|
-
|
37
|
+
@applicable_abilities ||= begin
|
38
|
+
namespace_abilities = fetch_namespace_abilities
|
39
|
+
namespace_abilities.select { |ability| ability_matches_current_action?(ability) }
|
40
40
|
end
|
41
41
|
end
|
42
|
+
|
43
|
+
# Get abilities for the current controller namespace, or root if no namespace
|
44
|
+
def fetch_namespace_abilities
|
45
|
+
namespace = controller_namespace.empty? ? '' : controller_namespace
|
46
|
+
Ability.where(namespace)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check if an ability matches the current controller action
|
50
|
+
def ability_matches_current_action?(ability)
|
51
|
+
current_controller_action = "#{controller_path}##{action_name}"
|
52
|
+
ability.actions.any? { |action| /#{action}/.match?(current_controller_action) }
|
53
|
+
end
|
42
54
|
end
|
43
55
|
end
|
44
56
|
end
|
@@ -3,18 +3,22 @@
|
|
3
3
|
# Generates a definition file for a namespace.
|
4
4
|
class PolicyDefinitionGenerator < Rails::Generators::Base
|
5
5
|
source_root File.expand_path('templates', __dir__)
|
6
|
-
desc 'Generates a policy definition file based on given namespace.'
|
7
|
-
argument :name, type: :string, required:
|
6
|
+
desc 'Generates a policy definition file based on given namespace. Leave empty for root-level definitions.'
|
7
|
+
argument :name, type: :string, required: false, default: '',
|
8
|
+
desc: 'The namespace for the definition file. Leave empty for root-level definitions.'
|
8
9
|
|
9
10
|
def create_policy_definition_file
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
if processed_name.blank?
|
12
|
+
# Generate root-level definitions.rb
|
13
|
+
template 'root_definitions.rb', 'app/policies/definitions.rb'
|
14
|
+
else
|
15
|
+
template 'modular_definitions.rb', "app/policies/#{processed_name}/definitions.rb"
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
15
19
|
private
|
16
20
|
|
17
21
|
def processed_name
|
18
|
-
@processed_name ||= name.strip.delete_prefix('/').delete_suffix('/')
|
22
|
+
@processed_name ||= name.strip.delete_prefix('/').delete_suffix('/') || ''
|
19
23
|
end
|
20
24
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Policies
|
4
|
+
DEFINITIONS = [
|
5
|
+
{
|
6
|
+
# Used as identifier for the policy.
|
7
|
+
code: 'Policy-Code',
|
8
|
+
# Readable name for the policy.
|
9
|
+
name: 'Readable Policy Name',
|
10
|
+
# Readable description for the policy.
|
11
|
+
description: 'Short description of what this policy allows',
|
12
|
+
# Controller actions the policy applies to. It works as matchers based on routes.
|
13
|
+
# It can be empty for manual policy checking.
|
14
|
+
actions: ['users', 'products#index', 'dashboard']
|
15
|
+
},
|
16
|
+
{
|
17
|
+
code: 'Another-Policy-Code',
|
18
|
+
name: 'Another Policy',
|
19
|
+
description: 'Long description.'
|
20
|
+
}
|
21
|
+
].freeze
|
22
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: controller_policies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tien
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -47,7 +47,8 @@ files:
|
|
47
47
|
- lib/controller_policies/railtie.rb
|
48
48
|
- lib/controller_policies/version.rb
|
49
49
|
- lib/generators/policy_definition_generator.rb
|
50
|
-
- lib/generators/templates/
|
50
|
+
- lib/generators/templates/modular_definitions.rb.tt
|
51
|
+
- lib/generators/templates/root_definitions.rb.tt
|
51
52
|
homepage: https://github.com/tieeeeen1994/rails-controller-policies
|
52
53
|
licenses:
|
53
54
|
- MIT
|
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
69
|
- !ruby/object:Gem::Version
|
69
70
|
version: '0'
|
70
71
|
requirements: []
|
71
|
-
rubygems_version: 3.
|
72
|
+
rubygems_version: 3.7.1
|
72
73
|
specification_version: 4
|
73
74
|
summary: Allows the developer to define policies for controllers.
|
74
75
|
test_files: []
|