kan 0.1.0 → 0.2.0

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
  SHA1:
3
- metadata.gz: d21ee95936ea431cd541add761806729598ee02d
4
- data.tar.gz: 3af71fe7b9a1e7b76d1e41955356d7c88fc2220b
3
+ metadata.gz: 4b05a0ee35f96b6d7cf1860821bbb9377ce6865d
4
+ data.tar.gz: 2c95160455b028aeddc5888a39659f61a2fef75e
5
5
  SHA512:
6
- metadata.gz: 51068bae77f496f2c4411540d2a99d5a30dcf12203cec91d5664760fd1e75f52f1c89efe7c65c61f5df428abea926874250a3a68e117a573016572583aa2fe79
7
- data.tar.gz: 168764d5406fceb5806388dff893d52fa5f41ba8c49445540140dfc962afbebe421f0eae5d1c0491654cf0814192346ba0317c941fa105b019dd67244013b14b
6
+ metadata.gz: e2d8dc36dfdfebf3da5f453824cc94b46f478346963d10d1d92c23873045cb2efb94debd260d5192e3233931236d09e80ede00b60e75e61b88b189e34fa55dc1
7
+ data.tar.gz: bc672426e146ad132e7339dd780c171e8da22ce350b70d9fc54a6bd1c1f9a5505faa64f5009c01d20036fab86ab37f19396b034314c2f4bb92d7f98aa76702c9
@@ -1,5 +1,13 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ cache: bundler
3
4
  rvm:
5
+ - 2.1.10
6
+ - 2.2.7
7
+ - 2.3.4
4
8
  - 2.4.1
5
- before_install: gem install bundler -v 1.16.0
9
+ - 2.5.0
10
+
11
+ before_install:
12
+ - gem install bundler -v 1.16.0
13
+ - gem update --system
@@ -0,0 +1,7 @@
1
+ ## HEAD
2
+
3
+ ## v0.2
4
+
5
+ * Add logger support (@valikos) #7
6
+ * Integrate CI (@davydovanton)
7
+ * Implement roles support (@davydovanton)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kan (0.1.0)
4
+ kan (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,6 +1,21 @@
1
1
  # Kan
2
+ [![Build Status](https://travis-ci.org/davydovanton/kan.svg?branch=master)](https://travis-ci.org/davydovanton/kan)
2
3
 
3
- Simple fundctional authorization library for ruby. Inspired by [transproc](https://github.com/solnic/transproc)
4
+ Simple functional authorization library for ruby. Inspired by [transproc](https://github.com/solnic/transproc) and [dry project](http://dry-rb.org)
5
+
6
+ ## Table of context
7
+
8
+ * [Installation](#installation)
9
+ * [Usage](#usage)
10
+ * [Register abilities](#register-abilities)
11
+ * [Check abilities](#check-abilities)
12
+ * [Default ability block](#default-ability-block)
13
+ * [List of abilities](#list-of-abilities)
14
+ * [Roles](#roles)
15
+ * [Dry-auto\_inject](#dry-auto_inject)
16
+ * [Contributing](#contributing)
17
+ * [License](#license)
18
+ * [Code of Conduct](#code-of-conduct)
4
19
 
5
20
  ## Installation
6
21
 
@@ -20,6 +35,8 @@ Or install it yourself as:
20
35
 
21
36
  ## Usage
22
37
 
38
+ ### Register abilities
39
+
23
40
  ```ruby
24
41
  class Post::Abilities
25
42
  include Kan::Abilities
@@ -28,8 +45,11 @@ class Post::Abilities
28
45
  register 'edit' { |user, post| user.id == post.user_id }
29
46
  register 'delete' { |_, _| false }
30
47
  end
48
+ ```
49
+
50
+ Also, you can register more than one ability in one place and use string or symbol keys:
31
51
 
32
- # register more than one ability in one place
52
+ ```ruby
33
53
  class Post::AdminAbilities
34
54
  include Kan::Abilities
35
55
 
@@ -46,38 +66,146 @@ class Comments::Abilities
46
66
  user.id == comment.user_id && comment.created_at < Time.now + TEN_MINUTES
47
67
  end
48
68
  end
69
+ ```
49
70
 
71
+ ### Check abilities
72
+
73
+ ```ruby
50
74
  abilities = Kan::Application.new(
51
75
  post: Post::Abilities.new,
52
- comment: Comments::Abilities.new,
76
+ comment: Comments::Abilities.new
53
77
  )
54
78
 
55
79
  abilities['post.read'].call(current_user, post) # => true
56
80
  abilities['post.delete'].call(current_user, post) # => false
57
-
58
81
  abilities['comment.delete'].call(current_user, post) # => false
82
+ ```
83
+
84
+ #### Default ability block
59
85
 
60
- # Default ability eq `proc { true }`
86
+ By default Kan use `proc { true }` as a default ability block:
87
+
88
+ ```ruby
61
89
  abilities['comment.invalid'].call(current_user, post) # => true
90
+ ```
91
+
92
+ But you can rewrite it
62
93
 
63
- # But you can rewrite default ability block
94
+ ```ruby
64
95
  admin_abilities = Kan::Application.new(
65
- post: Post::AdminAbilities.new(default_ability_block: proc { false}),
96
+ post: Post::AdminAbilities.new(default_ability_block: proc { false }),
66
97
  comment: Comments::Abilities.new,
67
98
  )
68
99
 
69
- admin_abilities['post.delete'].call(current_user, post) # => false
70
- admin_abilities['post.delete'].call(admin_user, post) # => true
100
+ admin_abilities['post.delete'].call(current_user, post) # => false
101
+ admin_abilities['post.delete'].call(admin_user, post) # => true
71
102
  admin_abilities['post.invalid'].call(current_user, post) # => false
103
+ ```
72
104
 
105
+ #### List of abilities
106
+ You can provide array of abilities for each scope and Kan will return `true` if at least one ability return `true`:
107
+
108
+ ```ruby
73
109
  global_abilities = Kan::Application.new(
74
110
  post: [Post::Abilities.new, Post::AdminAbilities.new],
75
- comment: Comments::Abilities.new,
111
+ comment: Comments::Abilities.new
76
112
  )
77
113
 
78
114
  global_abilities['post.edit'].call(current_user, post) # => false
79
- global_abilities['post.edit'].call(owner_user, post) # => true
80
- global_abilities['post.edit'].call(admin_user, post) # => true
115
+ global_abilities['post.edit'].call(owner_user, post) # => true
116
+ global_abilities['post.edit'].call(admin_user, post) # => true
117
+ ```
118
+
119
+ ### Roles
120
+ Kan provide simple role system. For this you need to define role block in each abilities classes:
121
+ ```ruby
122
+ module Post
123
+ class AnonymousAbilities
124
+ include Kan::Abilities
125
+
126
+ role :anonymous do |user, _|
127
+ user.id.nil?
128
+ end
129
+
130
+ register(:read, :edit, :delete) { false }
131
+ end
132
+
133
+ class BaseAbilities
134
+ include Kan::Abilities
135
+
136
+ role :all do |_, _|
137
+ true
138
+ end
139
+
140
+ register(:read) { |_, _| true }
141
+ register(:edit, :delete) { |user, post| false }
142
+ end
143
+
144
+
145
+ class AuthorAbilities
146
+ include Kan::Abilities
147
+
148
+ role :author do |user, post|
149
+ user.id == post.author_id
150
+ end
151
+
152
+ register(:read, :edit) { |_, _| true }
153
+ register(:delete) { |_, _| false }
154
+ end
155
+
156
+ class AdminAbilities
157
+ include Kan::Abilities
158
+
159
+ role :admin do |user, _|
160
+ user.admin?
161
+ end
162
+
163
+ register :read, :edit, :delete { |_, _| true }
164
+ end
165
+ end
166
+ ```
167
+
168
+ After that initialize Kan application object and call it with payload:
169
+ ```ruby
170
+ abilities = Kan::Application.new(
171
+ post: [Post::AnonymousAbilities.new, Post::BaseAbilities.new, Post::AuthorAbilities.new, Post::AdminAbilities.new],
172
+ comment: Comments::Abilities.new
173
+ )
174
+
175
+ abilities['post.read'].call(anonymous, post) # => false
176
+ abilities['post.read'].call(regular, post) # => true
177
+ abilities['post.read'].call(author, post) # => true
178
+ abilities['post.read'].call(admin, post) # => true
179
+
180
+ abilities['post.edit'].call(anonymous, post) # => false
181
+ abilities['post.edit'].call(regular, post) # => false
182
+ abilities['post.edit'].call(author, post) # => true
183
+ abilities['post.edit'].call(admin, post) # => true
184
+
185
+ abilities['post.delete'].call(anonymous, post) # => false
186
+ abilities['post.delete'].call(regular, post) # => false
187
+ abilities['post.delete'].call(author, post) # => false
188
+ abilities['post.delete'].call(admin, post) # => true
189
+ ```
190
+
191
+ ### Logger support
192
+ By default kan support default ruby logger (`Logger.new` class). For setup custom logger you can use `logger` option for each abilities instances:
193
+ ```ruby
194
+ abilities = Kan::Application.new(
195
+ comment: Comments::Abilities.new(logger: MyCustomLogger.new)
196
+ )
197
+ ```
198
+
199
+ And call it from ability block:
200
+ ```ruby
201
+ class AnonymousAbilities
202
+ include Kan::Abilities
203
+
204
+ register(:read, :edit, :delete) do
205
+ logger.info 'Anonymous ability checked'
206
+ false
207
+ end
208
+ end
81
209
  ```
82
210
 
83
211
  ### Dry-auto\_inject
@@ -105,10 +233,18 @@ UpdateOperation.new(ability_checker: ->(*) { false })
105
233
 
106
234
  Bug reports and pull requests are welcome on GitHub at https://github.com/davydovanton/kan. 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.
107
235
 
236
+ ### How to instal the project
237
+ Just clone repository and call:
238
+
239
+ ```
240
+ $ bundle install
241
+ $ bundle exec rspec
242
+ ```
243
+
108
244
  ## License
109
245
 
110
246
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
111
247
 
112
248
  ## Code of Conduct
113
249
 
114
- Everyone interacting in the Kan project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/kan/blob/master/CODE_OF_CONDUCT.md).
250
+ Everyone interacting in the Kan project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/davydovanton/kan/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
8
- end
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
9
8
 
10
- task :default => :test
9
+ task default: :spec
10
+ rescue LoadError
11
+ # no rspec available
12
+ end
data/lib/kan.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'kan/abilities_list'
1
2
  require 'kan/application'
2
3
  require 'kan/abilities'
3
4
  require 'kan/version'
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  module Kan
2
4
  module Abilities
3
5
  def self.included(base)
@@ -5,11 +7,31 @@ module Kan
5
7
  end
6
8
 
7
9
  module ClassMethods
10
+ DEFAULT_ROLE_NAME = :base
11
+ DEFAULT_ROLE_BLOCK = proc { true }
12
+
8
13
  def register(*abilities, &block)
9
14
  @ability_list ||= {}
10
15
  abilities.each { |ability| @ability_list[ability.to_sym] = block }
11
16
  end
12
17
 
18
+ def role(role_name, &block)
19
+ @role_name = role_name
20
+ @role_block = block
21
+ end
22
+
23
+ def role_name
24
+ @role_name || DEFAULT_ROLE_NAME
25
+ end
26
+
27
+ def role_block
28
+ @role_block || DEFAULT_ROLE_BLOCK
29
+ end
30
+
31
+ def valid_role?(*args)
32
+ role_block.call(*args)
33
+ end
34
+
13
35
  def ability_list
14
36
  @ability_list || {}
15
37
  end
@@ -17,12 +39,16 @@ module Kan
17
39
 
18
40
  DEFAULT_ABILITY_BLOCK = proc { true }
19
41
 
42
+ attr_reader :logger
43
+
20
44
  def initialize(options = {})
21
45
  @options = options
46
+ @logger = @options.fetch(:logger, Logger.new(STDOUT))
22
47
  end
23
48
 
24
49
  def ability(name)
25
- self.class.ability_list[name.to_sym] || @options[:default_ability_block] || DEFAULT_ABILITY_BLOCK
50
+ rule = self.class.ability_list[name.to_sym] || @options[:default_ability_block] || DEFAULT_ABILITY_BLOCK
51
+ lambda { |*args| instance_exec(args, &rule) }
26
52
  end
27
53
  end
28
54
  end
@@ -0,0 +1,14 @@
1
+ module Kan
2
+ class AbilitiesList
3
+ def initialize(name, list)
4
+ @name = name
5
+ @list = list
6
+ end
7
+
8
+ def call(*payload)
9
+ @list
10
+ .select { |abilities| abilities.class.valid_role?(*payload) }
11
+ .any? { |abilities| abilities.ability(@name).call(*payload) }
12
+ end
13
+ end
14
+ end
@@ -6,7 +6,17 @@ module Kan
6
6
 
7
7
  def [](ability)
8
8
  scope, ability_name = ability.split('.')
9
- @scopes[scope.to_sym].ability(ability_name)
9
+
10
+ abilities = Array(@scopes[scope.to_sym])
11
+ raise_scope_error(scope) if abilities.empty?
12
+
13
+ AbilitiesList.new(ability_name, abilities)
14
+ end
15
+
16
+ private
17
+
18
+ def raise_scope_error(scope)
19
+ raise ArgumentError.new("Invalid scope #{scope}")
10
20
  end
11
21
  end
12
22
  end
@@ -1,3 +1,3 @@
1
1
  module Kan
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Davydov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-01-09 00:00:00.000000000 Z
11
+ date: 2018-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -76,6 +76,7 @@ files:
76
76
  - ".gitignore"
77
77
  - ".rspec"
78
78
  - ".travis.yml"
79
+ - CHANGELOG.md
79
80
  - CODE_OF_CONDUCT.md
80
81
  - Gemfile
81
82
  - Gemfile.lock
@@ -85,6 +86,7 @@ files:
85
86
  - kan.gemspec
86
87
  - lib/kan.rb
87
88
  - lib/kan/abilities.rb
89
+ - lib/kan/abilities_list.rb
88
90
  - lib/kan/application.rb
89
91
  - lib/kan/version.rb
90
92
  homepage: https://github.com/davydovanton/kan