passpartu 0.5.0 → 1.0.2
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 +108 -17
- data/lib/passpartu.rb +58 -6
- data/lib/passpartu/block_verify.rb +2 -0
- data/lib/passpartu/patcher.rb +12 -4
- data/lib/passpartu/user.rb +2 -0
- data/lib/passpartu/validate_result.rb +2 -0
- data/lib/passpartu/verify.rb +33 -18
- data/lib/passpartu/version.rb +3 -1
- data/passpartu.gemspec +2 -2
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6941eb262c351f25dd525ced8ce9d27cf03c90f6977d9e31674b5aa675db2ada
|
4
|
+
data.tar.gz: e9fd5451850cc07bec8b6a2db6e23cadbc9415eb14945a39ab3913857c7e4628
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8986a0743c963712de307a141bfe37405636ec7f952e29ae13c22c2512ec5ae04ef0f4e93e29a8ee5ffd32ebcf39675c7800e58332b451acfc34444d875178a6
|
7
|
+
data.tar.gz: 5d1fde0b670dd30acd04bb819d503588cccaaacbae773076eee10fbd865a16fa370e822c1227d73388d588afd49f3cf96ad759a2db373ef8774d3206d39b3524
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
# Passpartu
|
1
|
+
# Passpartu v1.0.2 - [changelog](https://github.com/coaxsoft/passpartu/blob/master/CHANGELOG.md)
|
2
2
|
|
3
|
-
Passpartu makes policies great again (works awesome with Pundit).
|
3
|
+
Passpartu makes policies great again (works awesome with [Pundit](https://rubygems.org/gems/pundit)).
|
4
4
|
|
5
5
|
Instead of this:
|
6
|
+
|
6
7
|
```ruby
|
7
8
|
class PostPolicy < ApplicationPolicy
|
8
9
|
def update?
|
@@ -12,6 +13,7 @@ end
|
|
12
13
|
```
|
13
14
|
|
14
15
|
just this:
|
16
|
+
|
15
17
|
```ruby
|
16
18
|
class PostPolicy < ApplicationPolicy
|
17
19
|
def update?
|
@@ -19,23 +21,39 @@ class PostPolicy < ApplicationPolicy
|
|
19
21
|
end
|
20
22
|
end
|
21
23
|
```
|
24
|
+
|
22
25
|
## Usage
|
26
|
+
|
23
27
|
Include `Passpartu` into your policy model.
|
28
|
+
|
24
29
|
```ruby
|
25
30
|
class User
|
26
31
|
include Passpartu
|
27
32
|
end
|
28
33
|
```
|
34
|
+
|
29
35
|
NOTE: Your `User` model must respond to `role` method that returns a string or a symbol!
|
30
36
|
|
31
37
|
Keep all your policies in one place.
|
32
38
|
Create `./config/passpartu.yml` and start writing your policies.
|
33
39
|
|
34
|
-
#### Example
|
40
|
+
#### Example of `passpartu.yml`
|
41
|
+
|
35
42
|
```yml
|
36
43
|
# ./config/passpartu.yml
|
44
|
+
manager: &manager
|
45
|
+
order:
|
46
|
+
create: true
|
47
|
+
edit: true
|
48
|
+
delete: false
|
49
|
+
product:
|
50
|
+
create: true
|
51
|
+
edit: true
|
52
|
+
delete: false
|
37
53
|
|
54
|
+
# yaml files supports inheritance!
|
38
55
|
admin:
|
56
|
+
<<: *manager
|
39
57
|
post:
|
40
58
|
create: false
|
41
59
|
update: true
|
@@ -51,30 +69,51 @@ admin:
|
|
51
69
|
items:
|
52
70
|
crud: true
|
53
71
|
delete: false
|
54
|
-
manager:
|
55
|
-
order:
|
56
|
-
create: true
|
57
|
-
edit: true
|
58
|
-
delete: false
|
59
|
-
product:
|
60
|
-
create: true
|
61
|
-
edit: true
|
62
|
-
delete: false
|
63
|
-
|
64
72
|
```
|
65
|
-
|
73
|
+
|
74
|
+
## Features
|
75
|
+
|
76
|
+
#### CRUD
|
77
|
+
|
66
78
|
It's possible to use `crud` key to set values for `create`, `read`, `update`, `delete` at once.
|
67
79
|
`create`, `read`, `update`, `delete` has higher priority than `crud`
|
80
|
+
|
68
81
|
In case `crud: true` and `delete: false` - result `false`
|
69
82
|
|
70
83
|
|
71
|
-
####
|
72
|
-
|
84
|
+
#### Only
|
85
|
+
|
86
|
+
It's possible to include specific roles to checks
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
user_admin.can?(:orders, :edit) # check policy for admin and returns true if policy true
|
90
|
+
user_admin.can?(:orders, :edit, only: :admin) # returns true because the user is an admin and we included only admin
|
91
|
+
user_manager.can?(:orders, :edit, only: :admin) # returns false because user is manager and we included only admin
|
92
|
+
```
|
93
|
+
|
94
|
+
It's possible to give an array as only attribute
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
user_admin.can?(:orders, :edit, only: [:admin, :manager]) # returns true
|
98
|
+
user_manager.can?(:orders, :edit, only: [:admin, :manager]) # returns true
|
99
|
+
```
|
100
|
+
|
101
|
+
Note: `only` has higher priority than `except/skip`. Do not use both.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
user_admin.can?(:orders, :edit, only: :admin, except: :admin) # returns true
|
105
|
+
```
|
106
|
+
|
107
|
+
#### Skip (except)
|
108
|
+
|
109
|
+
It's possible to exclude roles from checks
|
110
|
+
|
73
111
|
```ruby
|
74
112
|
user_admin.can?(:orders, :edit) # check policy for admin and returns true if policy true
|
75
113
|
user_admin.can?(:orders, :edit, except: :admin) # returns false because user is admin and we excluded admin
|
76
114
|
|
77
115
|
```
|
116
|
+
|
78
117
|
It's possible to give an array as except attribute
|
79
118
|
|
80
119
|
```ruby
|
@@ -82,8 +121,20 @@ It's possible to give an array as except attribute
|
|
82
121
|
user_manager.can?(:orders, :edit, except: [:admin, :manager]) # returns false
|
83
122
|
```
|
84
123
|
|
124
|
+
`skip` alias to `except`
|
125
|
+
|
126
|
+
Note: `expect` has higher priority than `skip`. Do not use both.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
user_agent.can?(:orders, :edit, except: [:admin, :manager]) { user_agent.orders.include?(order) }
|
130
|
+
# equals to
|
131
|
+
user_agent.can?(:orders, :edit, skip: [:admin, :manager]) { user_agent.orders.include?(order) }
|
132
|
+
```
|
133
|
+
|
85
134
|
#### Per role methods
|
135
|
+
|
86
136
|
Check user roles AND policy rule
|
137
|
+
|
87
138
|
```ruby
|
88
139
|
# check if user admin AND returns true if policy true
|
89
140
|
user_admin.admin_can?(:orders, :edit) # true
|
@@ -93,6 +144,7 @@ Check user roles AND policy rule
|
|
93
144
|
```
|
94
145
|
|
95
146
|
#### Code blocks
|
147
|
+
|
96
148
|
```ruby
|
97
149
|
# check rules as usual AND code in the block
|
98
150
|
user_agent.can?(:orders, :edit, except: [:admin, :manager]) { user_agent.orders.include?(order) }
|
@@ -101,8 +153,42 @@ Check user roles AND policy rule
|
|
101
153
|
user_agent.agent_can?(:orders, :edit, except: [:admin, :manager]) { user_agent.orders.include?(order) }
|
102
154
|
```
|
103
155
|
|
156
|
+
#### Waterfall check
|
157
|
+
|
158
|
+
Allow or restrict absolutely everything for particular role or/and particular domain.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
# ./config/initializers/passpartu.rb
|
162
|
+
|
163
|
+
Passpartu.configure do |config|
|
164
|
+
config.check_waterfall = true
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
```yml
|
169
|
+
# ./config/passpartu.yml
|
170
|
+
|
171
|
+
super_admin: true
|
172
|
+
super_looser: false
|
173
|
+
medium_looser:
|
174
|
+
orders:
|
175
|
+
create: true
|
176
|
+
delete: false
|
177
|
+
products: true
|
178
|
+
```
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
user_super_admin.can?(:do, :whatever, :want) # true
|
182
|
+
user_super_loser.can?(:do, :whatever, :want) # false
|
183
|
+
user_medium_loser.can?(:orders, :create) # true
|
184
|
+
user_medium_loser.can?(:orders, :delete) # false
|
185
|
+
user_medium_loser.can?(:products, :create) # true
|
186
|
+
user_medium_loser.can?(:products, :create, :and_delete) # true
|
187
|
+
```
|
104
188
|
##### Real life example
|
189
|
+
|
105
190
|
You need to check custom rule for agent
|
191
|
+
|
106
192
|
```yml
|
107
193
|
# ./config/passpartu.yml
|
108
194
|
|
@@ -140,9 +226,13 @@ You can configure Passpartu by creating `./config/initializers/passpartu.rb`.
|
|
140
226
|
Passpartu.configure do |config|
|
141
227
|
config.policy_file = './config/passpartu.yml'
|
142
228
|
config.raise_policy_missed_error = true
|
229
|
+
config.check_waterfall = false
|
143
230
|
end
|
231
|
+
|
144
232
|
```
|
233
|
+
|
145
234
|
### Raise policy missed errors
|
235
|
+
|
146
236
|
By default Passpartu will raise an PolicyMissedError if policy is missed in `passpartu.yml`. In initializer set `config.raise_policy_missed_error = false` in order to return `false` in case when policy is not defined. This is a good approach to write only "positive" policies (only true) and automatically restricts everything that is not mentioned in `passpartu.yml`
|
147
237
|
|
148
238
|
## Installation
|
@@ -171,7 +261,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
171
261
|
|
172
262
|
## Contributing
|
173
263
|
|
174
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
264
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/coaxsoft/passpartu. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
175
265
|
|
176
266
|
## License
|
177
267
|
|
@@ -182,4 +272,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
182
272
|
Everyone interacting in the Passpartu project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/coaxsoft/passpartu/blob/master/CODE_OF_CONDUCT.md).
|
183
273
|
|
184
274
|
## Idea
|
275
|
+
|
185
276
|
Initially designed and created by [Orest Falchuk](https://github.com/OrestF)
|
data/lib/passpartu.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'passpartu/version'
|
2
4
|
require 'yaml'
|
3
5
|
require_relative 'passpartu/patcher'
|
@@ -8,6 +10,8 @@ require_relative 'passpartu/user' # for testing only
|
|
8
10
|
|
9
11
|
module Passpartu
|
10
12
|
class Error < StandardError; end
|
13
|
+
class PolicyYmlNotFoundError < StandardError; end
|
14
|
+
class WaterfallError < StandardError; end
|
11
15
|
|
12
16
|
def self.included(policy_class)
|
13
17
|
Passpartu::Patcher.call(policy_class)
|
@@ -27,18 +31,66 @@ module Passpartu
|
|
27
31
|
end
|
28
32
|
|
29
33
|
class Config
|
30
|
-
attr_accessor :
|
31
|
-
attr_reader :policy_file
|
34
|
+
attr_accessor :raise_policy_missed_error
|
35
|
+
attr_reader :policy_file, :check_waterfall, :policy
|
36
|
+
|
37
|
+
DEFAULT_POLICY_FILE = './config/passpartu.yml'
|
32
38
|
|
33
39
|
def initialize
|
34
|
-
@policy_file =
|
35
|
-
|
40
|
+
@policy_file = DEFAULT_POLICY_FILE
|
41
|
+
set_policy(YAML.load_file(policy_file)) if File.exist?(policy_file)
|
36
42
|
@raise_policy_missed_error = true
|
43
|
+
@check_waterfall = false
|
37
44
|
end
|
38
45
|
|
39
46
|
def policy_file=(file = nil)
|
40
|
-
@policy_file = file ||
|
41
|
-
|
47
|
+
@policy_file = file || DEFAULT_POLICY_FILE
|
48
|
+
|
49
|
+
raise PolicyYmlNotFoundError unless File.exist?(policy_file)
|
50
|
+
|
51
|
+
set_policy(YAML.load_file(policy_file))
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_waterfall=(value)
|
55
|
+
@check_waterfall = value
|
56
|
+
|
57
|
+
if @check_waterfall
|
58
|
+
@raise_policy_missed_error = false
|
59
|
+
set_policy(@policy)
|
60
|
+
end
|
61
|
+
|
62
|
+
@check_waterfall
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def set_policy(value)
|
68
|
+
@policy = patch_policy_booleans_if(value)
|
69
|
+
end
|
70
|
+
|
71
|
+
# patch all booleans in hash to support check_waterfall
|
72
|
+
def patch_policy_booleans_if(hash)
|
73
|
+
return hash unless @check_waterfall
|
74
|
+
|
75
|
+
hash.transform_values! do |value|
|
76
|
+
if value.is_a?(TrueClass)
|
77
|
+
value.define_singleton_method(:dig) { |*_keys| true }
|
78
|
+
elsif value.is_a?(FalseClass)
|
79
|
+
value.define_singleton_method(:dig) { |*_keys| false }
|
80
|
+
elsif
|
81
|
+
patch_policy_booleans_if(value)
|
82
|
+
end
|
83
|
+
|
84
|
+
value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def blank?(item)
|
89
|
+
item.respond_to?(:empty?) ? !!item.empty? : !item
|
90
|
+
end
|
91
|
+
|
92
|
+
def present?(item)
|
93
|
+
!blank?(item)
|
42
94
|
end
|
43
95
|
end
|
44
96
|
|
data/lib/passpartu/patcher.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Passpartu
|
2
4
|
class Patcher
|
3
5
|
attr_reader :klass
|
4
6
|
def initialize(klass)
|
7
|
+
raise PolicyYmlNotFoundError if Passpartu.policy.nil?
|
8
|
+
|
5
9
|
@klass = klass
|
6
10
|
end
|
7
11
|
|
@@ -11,13 +15,17 @@ module Passpartu
|
|
11
15
|
|
12
16
|
def call
|
13
17
|
klass.class_eval do
|
14
|
-
define_method('can?') do |*keys, except: nil, &block|
|
15
|
-
Passpartu::BlockVerify.call(role, keys, except: except, &block)
|
18
|
+
define_method('can?') do |*keys, only: nil, except: nil, skip: nil, &block|
|
19
|
+
Passpartu::BlockVerify.call(role, keys, only: only, except: except, skip: skip, &block)
|
16
20
|
end
|
17
21
|
|
18
22
|
Passpartu.policy.keys.each do |policy_role|
|
19
|
-
define_method("#{policy_role}_can?") do |*keys, except: nil, &block|
|
20
|
-
role.to_s == policy_role && Passpartu::BlockVerify.call(role, keys,
|
23
|
+
define_method("#{policy_role}_can?") do |*keys, only: nil, except: nil, skip: nil, &block|
|
24
|
+
role.to_s == policy_role && Passpartu::BlockVerify.call(role, keys,
|
25
|
+
only: only,
|
26
|
+
except: except,
|
27
|
+
skip: skip,
|
28
|
+
&block)
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
data/lib/passpartu/user.rb
CHANGED
data/lib/passpartu/verify.rb
CHANGED
@@ -1,47 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Passpartu
|
2
4
|
class Verify
|
3
|
-
CRUD_KEY = 'crud'
|
5
|
+
CRUD_KEY = 'crud'
|
6
|
+
|
7
|
+
attr_reader :role, :keys, :result, :only, :except, :block
|
8
|
+
|
9
|
+
def initialize(role, keys, only, except, skip, block)
|
10
|
+
exclusion = except || skip # alias
|
4
11
|
|
5
|
-
attr_reader :role, :keys, :result, :except, :block
|
6
|
-
def initialize(role, keys, except, block)
|
7
12
|
@role = role.to_s
|
8
13
|
@keys = keys.map(&:to_s)
|
9
|
-
@
|
14
|
+
@only = Array(only).map(&:to_s) if present?(only)
|
15
|
+
@except = Array(exclusion).map(&:to_s) if present?(exclusion) && !@only
|
10
16
|
@block = block
|
17
|
+
|
18
|
+
raise PolicyYmlNotFoundError if Passpartu.policy.nil?
|
11
19
|
end
|
12
20
|
|
13
|
-
def self.call(role, keys, except: nil, &block)
|
14
|
-
new(role, keys, except, block).call
|
21
|
+
def self.call(role, keys, only: nil, except: nil, skip: nil, &block)
|
22
|
+
new(role, keys, only, except, skip, block).call
|
15
23
|
end
|
16
24
|
|
17
25
|
def call
|
18
|
-
return false if
|
26
|
+
return false if role_ignore?
|
19
27
|
|
20
|
-
|
21
|
-
|
28
|
+
default_check
|
29
|
+
check_crud_if
|
22
30
|
|
23
31
|
validate_result
|
32
|
+
rescue StandardError => e
|
33
|
+
if ['TrueClass does not have #dig method', 'FalseClass does not have #dig method'].include?(e.message)
|
34
|
+
raise WaterfallError.new "Looks like you want to use check_waterfall feature, but it's set to 'false'. Otherwise check your #{Passpartu.config.policy_file} for validness"
|
35
|
+
else
|
36
|
+
raise e
|
37
|
+
end
|
24
38
|
end
|
25
39
|
|
26
40
|
private
|
27
41
|
|
28
|
-
def
|
29
|
-
return
|
42
|
+
def role_ignore?
|
43
|
+
return !only.include?(role) if present?(only)
|
44
|
+
return except.include?(role) if present?(except)
|
30
45
|
|
31
|
-
|
46
|
+
false
|
32
47
|
end
|
33
48
|
|
34
|
-
def
|
49
|
+
def default_check
|
50
|
+
return unless policy_missed?
|
51
|
+
|
35
52
|
@result = Passpartu.policy.dig(role, *keys)
|
36
53
|
end
|
37
54
|
|
38
|
-
def
|
39
|
-
|
40
|
-
check_policy
|
41
|
-
end
|
55
|
+
def check_crud_if
|
56
|
+
return unless policy_missed? && last_key_crud?
|
42
57
|
|
43
|
-
def change_crud_key
|
44
58
|
@keys[-1] = CRUD_KEY
|
59
|
+
default_check
|
45
60
|
end
|
46
61
|
|
47
62
|
def policy_missed?
|
data/lib/passpartu/version.rb
CHANGED
data/passpartu.gemspec
CHANGED
@@ -17,8 +17,8 @@ Gem::Specification.new do |spec|
|
|
17
17
|
|
18
18
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
19
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
|
-
# if spec.respond_to?(:metadata)
|
21
20
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# if spec.respond_to?(:metadata)
|
22
22
|
|
23
23
|
# spec.metadata["homepage_uri"] = spec.homepage
|
24
24
|
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
@@ -39,6 +39,6 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.files = Dir['README.md', 'lib/**/*', 'lib/*', 'passpartu.gemspec']
|
40
40
|
|
41
41
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
42
|
-
spec.add_development_dependency 'rake', '~>
|
42
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
43
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
44
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: passpartu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OrestF
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '12.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '12.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,8 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
89
|
- !ruby/object:Gem::Version
|
90
90
|
version: '0'
|
91
91
|
requirements: []
|
92
|
-
|
93
|
-
rubygems_version: 2.7.3
|
92
|
+
rubygems_version: 3.0.1
|
94
93
|
signing_key:
|
95
94
|
specification_version: 4
|
96
95
|
summary: Passpartu makes policies great again
|