passpartu 0.5.0 → 1.0.2

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