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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd628d50b6e52acefe4f503c5ada9cec0b224b70be8791fa07f8c5586f93def4
4
- data.tar.gz: 5314cf52de8c7cf581768ed8842edb1164b3a57c9aa56456a5ed8e5f0dbfd1e9
3
+ metadata.gz: 6941eb262c351f25dd525ced8ce9d27cf03c90f6977d9e31674b5aa675db2ada
4
+ data.tar.gz: e9fd5451850cc07bec8b6a2db6e23cadbc9415eb14945a39ab3913857c7e4628
5
5
  SHA512:
6
- metadata.gz: 7ddafd099269680b9f05dbe140fdd9ebae94eff8072e81073cd5db3840bc8d87d4a8171db637de242df0f19c16043123972f9a8b518228d2e2712aab6559385c
7
- data.tar.gz: 3f3cab407da0abde8da14764df233c625020d12f8c0d631912d6d009c90bc6fb69d23024aa7d272dc3247009824160df64b816561ec05be43d0fc630b5687d7d
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
- #### Crud
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
- #### Except
72
- It's possible to exclude role from checks
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/OrestF/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.
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)
@@ -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 :policy, :raise_policy_missed_error
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 = './config/passpartu.yml'
35
- @policy = YAML.load_file(policy_file)
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 || './config/passpartu.yml'
41
- @policy = YAML.load_file(policy_file)
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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passpartu
2
4
  class BlockVerify < ::Passpartu::Verify
3
5
  def call
@@ -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, except: except, &block)
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # for testing only
2
4
 
3
5
  module Passpartu
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passpartu
2
4
  class ValidateResult
3
5
  class PolicyMissedError < StandardError; end
@@ -1,47 +1,62 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passpartu
2
4
  class Verify
3
- CRUD_KEY = 'crud'.freeze
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
- @except = Array(except).map(&:to_s) if present?(except)
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 role_excepted?
26
+ return false if role_ignore?
19
27
 
20
- check_policy
21
- check_crud if policy_missed? && last_key_crud?
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 role_excepted?
29
- return false if blank?(except)
42
+ def role_ignore?
43
+ return !only.include?(role) if present?(only)
44
+ return except.include?(role) if present?(except)
30
45
 
31
- except.include?(role)
46
+ false
32
47
  end
33
48
 
34
- def check_policy
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 check_crud
39
- change_crud_key
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?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passpartu
2
- VERSION = '0.5.0'.freeze
4
+ VERSION = '1.0.2'
3
5
  end
@@ -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', '~> 10.0'
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.5.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: 2019-03-20 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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
- rubyforge_project:
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