sevencan 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +30 -0
- data/Gemfile +1 -1
- data/README.md +217 -98
- data/gemfiles/activesupport-4.0 +6 -0
- data/gemfiles/activesupport-4.1 +6 -0
- data/gemfiles/activesupport-4.2 +6 -0
- data/gemfiles/activesupport-5.0 +6 -0
- data/gemfiles/activesupport-5.1 +6 -0
- data/lib/seven/abilities.rb +3 -3
- data/lib/seven/manager.rb +32 -4
- data/lib/seven/memory_store.rb +25 -0
- data/lib/seven/redis_store.rb +42 -0
- data/lib/seven/version.rb +1 -1
- data/lib/seven.rb +3 -0
- data/seven.gemspec +4 -3
- metadata +19 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b220308d46b28e4232122be61e42d6bf78d30b77
|
4
|
+
data.tar.gz: 990548a985b65bc327b3944a91b3c178c90b32db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca0e4a6f5a4de9f456e1d1bbe5e6d86c0a4d8248d58121263ca1051834a1b3d3273da6cfe2ff427fd92eebbb108c8291fb4dcd69ccc6cdaebcb4936f1a110c67
|
7
|
+
data.tar.gz: 746f524f2e012be8497df9674db0924029899f8f8bb4dabbc9f3816ca9048c807993ddc1eec36becfde1b74ba44fb01ede241e9af3c388ba9127c2334ce6ae71
|
data/.travis.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
sudo: false
|
2
|
+
dist: trusty
|
3
|
+
language: ruby
|
4
|
+
|
5
|
+
cache: bundler
|
6
|
+
|
7
|
+
rvm:
|
8
|
+
- 2.1.10
|
9
|
+
- 2.2.7
|
10
|
+
- 2.3.4
|
11
|
+
- 2.4.1
|
12
|
+
|
13
|
+
gemfile:
|
14
|
+
- gemfiles/activesupport-4.0
|
15
|
+
- gemfiles/activesupport-4.1
|
16
|
+
- gemfiles/activesupport-4.2
|
17
|
+
- gemfiles/activesupport-5.0
|
18
|
+
- gemfiles/activesupport-5.1
|
19
|
+
|
20
|
+
matrix:
|
21
|
+
exclude:
|
22
|
+
- {rvm: '2.1.10', gemfile: 'gemfiles/activesupport-5.0'}
|
23
|
+
- {rvm: '2.1.10', gemfile: 'gemfiles/activesupport-5.1'}
|
24
|
+
- {rvm: '2.4.1', gemfile: 'gemfiles/activesupport-4.0'}
|
25
|
+
- {rvm: '2.4.1', gemfile: 'gemfiles/activesupport-4.1'}
|
26
|
+
|
27
|
+
before_install:
|
28
|
+
- gem install bundler --no-doc
|
29
|
+
|
30
|
+
script: bundle exec rspec
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Seven
|
2
2
|
|
3
|
-
|
3
|
+
Define and verify Permissions.
|
4
|
+
|
5
|
+
[![Build Status](https://travis-ci.org/xiejiangzhi/seven.svg?branch=master)](https://travis-ci.org/xiejiangzhi/seven)
|
6
|
+
[![Gem Version](https://badge.fury.io/rb/sevencan.svg)](https://badge.fury.io/rb/sevencan)
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
@@ -18,56 +21,96 @@ Or install it yourself as:
|
|
18
21
|
|
19
22
|
$ gem install sevencan
|
20
23
|
|
24
|
+
|
21
25
|
## Usage
|
22
26
|
|
23
|
-
|
27
|
+
### Create your manager
|
24
28
|
|
25
29
|
```
|
26
|
-
|
27
|
-
manager = Seven::Manager.new(store: {redis: Redis.current}) # redis store
|
28
|
-
manager = Seven::Manager.new(store: {activerecord: UserAbility}) # db store
|
30
|
+
$abilities_manager = Seven::Manager.new
|
29
31
|
```
|
30
32
|
|
31
|
-
|
33
|
+
You can put it to `config/initializers/abilities.rb` if you on Rails
|
34
|
+
|
35
|
+
|
36
|
+
### Define your rules
|
37
|
+
|
38
|
+
On Rails, we can save these rules as app/abilities/*_abilities.rb
|
39
|
+
|
40
|
+
A simple example
|
32
41
|
|
33
42
|
```
|
34
|
-
|
35
|
-
|
36
|
-
|
43
|
+
class TopicAbilities
|
44
|
+
include Seven::Abilities
|
45
|
+
|
46
|
+
# it has some instance methods:
|
47
|
+
# current_user: a user instance of nil
|
48
|
+
# target: verify current_user permissions with this target. It's a Topic or a instance of Topic here
|
49
|
+
|
50
|
+
|
51
|
+
# if target if Topic or a instance of Topic, we will use this class to verify ability
|
52
|
+
$abilities_manager.define_rules(Topic, TopicAbilities)
|
53
|
+
|
54
|
+
# anyone can read list of topics(index action), non-login user also can read it
|
55
|
+
abilities do
|
56
|
+
can :read_topics
|
57
|
+
end
|
58
|
+
|
59
|
+
# define some abilities if the user logined
|
60
|
+
abilities pass: Proc.new { current_user } do
|
61
|
+
# user can read show page and create a new topic.(show, new and create action)
|
62
|
+
can :read_topic, :create_topic
|
63
|
+
|
64
|
+
subject = target.is_a?(Topic) ? Topic.new : target # Maybe the target is a Topic class
|
65
|
+
# user can edit and destroy own topic.(edit, update and destroy action)
|
66
|
+
can :edit_topic, :delete_topic if subject.user_id == current_user.id
|
67
|
+
end
|
68
|
+
|
69
|
+
# define some abilities if current_user is admin. `%w{admin}.include?(current_user.role)`
|
70
|
+
abilities check: :role, in: %w{admin} do
|
71
|
+
# admin can edit and delete all topics
|
72
|
+
can :edit_topic, :delete_topic
|
73
|
+
end
|
37
74
|
end
|
75
|
+
```
|
76
|
+
|
77
|
+
You also can write saome complex rules
|
38
78
|
|
79
|
+
```
|
39
80
|
# Topic and Topic instances
|
40
|
-
class
|
81
|
+
class TopicAbilities
|
41
82
|
include Seven::Abilities
|
42
83
|
|
43
|
-
|
44
|
-
|
45
|
-
#
|
84
|
+
$abilities_manager.define_rules(Topic, TopicAbilities)
|
85
|
+
|
86
|
+
# we will define some abilities for any user(user instance or nil) in this block
|
46
87
|
abilities do
|
47
88
|
can(:read_topic)
|
48
89
|
can_manager_topic if target_topic.user_id == current_user.id
|
49
|
-
|
50
90
|
cannot_manager_topic if target_topic.is_lock
|
51
91
|
end
|
52
92
|
|
53
|
-
# if
|
54
|
-
|
93
|
+
# if current user(current_user isn't nil) and %i{admin editor}.include?(current_user.role)
|
94
|
+
# we will define some abilities for the user
|
95
|
+
abilities check: :role, in: %i{admin editor} do
|
55
96
|
can_manager_topic
|
56
97
|
end
|
57
98
|
|
58
|
-
# current_user.role
|
99
|
+
# current_user.role is :reviewer
|
59
100
|
abilities check: :role, equal: :reviewer do
|
60
101
|
can :review_topic
|
61
102
|
end
|
62
103
|
|
63
|
-
|
104
|
+
# Of course, you also can use your rule.
|
105
|
+
# For example, we will give current_user some abilities if our proc doesn't return a false or nil value
|
106
|
+
abilities pass: Proc.new { current_user && target.user_id.nil? } do
|
64
107
|
can_manager_topic
|
65
108
|
end
|
66
109
|
|
67
|
-
|
110
|
+
# And you can move that proc to a instance method
|
111
|
+
abilities pass: :my_filter do
|
68
112
|
end
|
69
113
|
|
70
|
-
|
71
114
|
def can_manager_topic
|
72
115
|
can :edit_topic, :destroy_topic
|
73
116
|
end
|
@@ -76,64 +119,68 @@ class MyTopicAbilities
|
|
76
119
|
cannot :edit_topic, :destroy_topic
|
77
120
|
end
|
78
121
|
|
79
|
-
def
|
80
|
-
current_user.
|
122
|
+
def my_filter
|
123
|
+
current_user && target.user_id.nil?
|
81
124
|
end
|
82
125
|
end
|
126
|
+
```
|
83
127
|
|
84
|
-
|
128
|
+
You can define some abilities for all objects
|
85
129
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
can(:edit_user) if target.id == current_user.id
|
90
|
-
can(:destroy_user) if current_user.is_admin?
|
91
|
-
end
|
130
|
+
```
|
131
|
+
# for all objects, they're global rules
|
132
|
+
$abilities_manager.define_rules(Object, YourAbilities)
|
92
133
|
```
|
93
134
|
|
94
|
-
|
135
|
+
Use a block to define some abilities
|
95
136
|
|
96
137
|
```
|
97
|
-
|
98
|
-
|
99
|
-
|
138
|
+
$abilities_manager.define_rules(User) do
|
139
|
+
can(:read_user)
|
140
|
+
if current_user
|
141
|
+
can(:edit_user) if target.id == current_user.id
|
142
|
+
can(:destroy_user) if current_user.is_admin?
|
143
|
+
end
|
144
|
+
end
|
100
145
|
```
|
101
146
|
|
102
|
-
Check abilities
|
103
147
|
|
104
|
-
|
148
|
+
|
149
|
+
### Verify user abilities
|
150
|
+
|
151
|
+
No target
|
105
152
|
|
106
153
|
```
|
107
154
|
manager.define_rules(Object) { can :read_topics }
|
108
|
-
manager.can?(current_user, :read_topics) # true
|
109
|
-
manager.can?(nil, :read_topics) # true
|
110
|
-
manager.can?(current_user, :read_user) # false
|
155
|
+
manager.can?(current_user, :read_topics, nil) # true, target is nil
|
156
|
+
manager.can?(nil, :read_topics) # true, anyone can read_topics
|
111
157
|
|
158
|
+
manager.can?(current_user, :read_user) # false, we didn't define this abilities
|
112
159
|
manager.can?(current_user, :edit_user) # false
|
113
160
|
|
114
|
-
manager.
|
161
|
+
manager.store.add(user.id, :edit_user, true)
|
115
162
|
manager.can?(current_user, :edit_user) # true
|
116
163
|
manager.can?(nil, :edit_user) # true
|
117
164
|
```
|
118
165
|
|
119
|
-
|
166
|
+
Verify abilities for a class or its instances
|
120
167
|
|
121
168
|
```
|
122
169
|
manager.define_rules(Topic) { can :read_topics }
|
123
|
-
manager.can?(nil, :read_topics, Topic) # true
|
124
|
-
manager.can?(nil, :read_topics, Topic.first) # true
|
170
|
+
manager.can?(nil, :read_topics, Topic) # true, for Topic class
|
171
|
+
manager.can?(nil, :read_topics, Topic.first) # true, for instance of Topic
|
125
172
|
manager.can?(current_user, :read_topics, Topic.first) # true
|
126
173
|
manager.can?(current_user, :read_topics) # false
|
127
|
-
manager.can?(nil, :read_topics) # false
|
174
|
+
manager.can?(nil, :read_topics) # false, it's target is nil, it isn't a topic
|
128
175
|
|
129
|
-
manager.
|
176
|
+
manager.store.add(user.id, :edit_user, true)
|
130
177
|
manager.can?(current_user, :edit_user, User) # true
|
131
178
|
manager.can?(current_user, :edit_user, User.first) # true
|
132
|
-
manager.can?(current_user, :edit_user) #
|
179
|
+
manager.can?(current_user, :edit_user) # true
|
133
180
|
manager.can?(nil, :edit_user) # false
|
134
181
|
```
|
135
182
|
|
136
|
-
|
183
|
+
Define and verify abilities for a instance(TODO)
|
137
184
|
|
138
185
|
```
|
139
186
|
manager.define_rules(Topic.first) { can :read_topics }
|
@@ -143,93 +190,94 @@ manager.can?(current_user, :read_topics, Topic.first) # true
|
|
143
190
|
manager.can?(current_user, :read_topics, Topic.last) # false
|
144
191
|
manager.can?(current_user, :read_topics) # false
|
145
192
|
manager.can?(nil, :read_topics) # false
|
146
|
-
|
147
|
-
manager.add_dynamic_rule(user, :edit_user, User.first)
|
148
|
-
manager.can?(current_user, :edit_user, User) # false
|
149
|
-
manager.can?(current_user, :edit_user, User.first) # true
|
150
|
-
manager.can?(current_user, :edit_user, User.last) # false
|
151
|
-
manager.can?(current_user, :edit_user) # false
|
152
|
-
manager.can?(nil, :edit_user) # false
|
153
193
|
```
|
154
194
|
|
155
195
|
|
156
196
|
## Rails
|
157
197
|
|
158
|
-
|
159
|
-
### Init manager
|
198
|
+
### Init your manager
|
160
199
|
|
161
200
|
in `config/initializers/seven_abilities.rb`
|
162
201
|
|
163
202
|
```
|
164
203
|
$abilities_manager = Seven::Manager.new
|
165
|
-
Dir[Rails.root.join('app/abilities/**/*.rb')].each {|file| require file }
|
204
|
+
Dir[Rails.root.join('app/abilities/**/*.rb')].each { |file| require file }
|
166
205
|
```
|
167
206
|
|
168
|
-
Define rules in `app/abilities/*.rb`
|
207
|
+
Define some rules in `app/abilities/*.rb`
|
169
208
|
|
170
209
|
```
|
171
|
-
class
|
210
|
+
class MyAbilities
|
172
211
|
include Seven::Abilities
|
173
212
|
|
174
|
-
$abilities_manager.define_rules(
|
213
|
+
$abilities_manager.define_rules(MyObject, MyAbilities)
|
175
214
|
|
176
|
-
# define rules
|
215
|
+
# define some rules
|
177
216
|
end"
|
178
217
|
```
|
179
218
|
|
180
|
-
### Require methods
|
181
219
|
|
182
|
-
|
183
|
-
* `abilities_manager`: return `Seven::Manager` instance
|
184
|
-
* `ability_check_callback`: call the method after check
|
220
|
+
### ControllerHelpers
|
185
221
|
|
222
|
+
We need these methods of controller to check user ability
|
186
223
|
|
187
|
-
|
224
|
+
* `current_user`: It is MyAbilities#current_user
|
225
|
+
* `abilities_manager`: You need return a instance of `Seven::Manager`
|
226
|
+
* `ability_check_callback`: We will call it after verifying
|
227
|
+
|
228
|
+
|
229
|
+
For example:
|
188
230
|
|
189
231
|
```
|
190
232
|
class ApplicationController < ActionController::Base
|
191
|
-
#
|
192
|
-
#
|
193
|
-
# `
|
233
|
+
# when you include `Seven::Rails::ControllerHelpers` module, it will do something below
|
234
|
+
# define `can?` instance method and `seven_ability_check` methods for your controller
|
235
|
+
# define `seven_ability_check_filter` instance method, it's callback of before_action for Seven
|
236
|
+
# define `seven_ability_check` class methods, it will call `before_action :seven_ability_check_filter` and store some your options
|
194
237
|
include Seven::Rails::ControllerHelpers
|
195
238
|
|
196
239
|
def abilities_manager
|
197
|
-
$
|
240
|
+
$abilities_manager
|
198
241
|
end
|
199
242
|
|
200
|
-
def ability_check_callback(
|
201
|
-
#
|
202
|
-
# ability:
|
203
|
-
# target:
|
243
|
+
def ability_check_callback(is_allowed, ability, target)
|
244
|
+
# is_allowed: true or false, is_allowed is true when user can access this action
|
245
|
+
# ability: ability of this action, like :read_topic
|
246
|
+
# target: resource object of this action
|
247
|
+
redirect_to root_path notice: 'Permission denied' unless is_allowed
|
204
248
|
end
|
205
249
|
end
|
206
250
|
```
|
207
251
|
|
208
|
-
|
252
|
+
Verify permissions for default actions, we will get a ability name according to controller name.
|
253
|
+
|
254
|
+
Some mapping examples:
|
255
|
+
|
256
|
+
* TopicController#index => :read_topics
|
257
|
+
* TopicController#show => :read_topic
|
258
|
+
* UserController#new => :create_user
|
259
|
+
* UserController#create => :create_user
|
260
|
+
* UserController#edit => :edit_user
|
261
|
+
* UserController#update => :edit_user
|
262
|
+
* UserController#destroy => :delete_user
|
263
|
+
|
209
264
|
|
210
265
|
```
|
211
266
|
class TopicController < ApplicationController
|
212
267
|
before_action :find_topic
|
213
268
|
|
214
|
-
# if
|
215
|
-
seven_ability_check [:@topic, Proc.new {
|
269
|
+
# if exists @topic, target is @topic, else use the result of proc, use Topic if the proc return nil
|
270
|
+
seven_ability_check [:@topic, Proc.new { nil }, Topic]
|
216
271
|
|
217
|
-
#
|
272
|
+
# Seven will automitically checks current_user has read_topics of Topic
|
273
|
+
# We have no @topic and proc is nil, the Topic is our target
|
218
274
|
def index
|
219
275
|
end
|
220
276
|
|
221
|
-
#
|
277
|
+
# check current_user can read_topic of @topic
|
222
278
|
def show
|
223
279
|
end
|
224
280
|
|
225
|
-
# Other actions:
|
226
|
-
# new: create_topic of Topic
|
227
|
-
# create: create_topic of Topic
|
228
|
-
# edit: edit_topic of @topic
|
229
|
-
# update: edit_topic of @topic
|
230
|
-
# destory: delete_topic of @topic
|
231
|
-
|
232
|
-
|
233
281
|
private
|
234
282
|
|
235
283
|
def find_topic
|
@@ -238,7 +286,7 @@ class TopicController < ApplicationController
|
|
238
286
|
end
|
239
287
|
```
|
240
288
|
|
241
|
-
|
289
|
+
Set a customized ability for actions
|
242
290
|
|
243
291
|
```
|
244
292
|
class TopicController < ApplicationController
|
@@ -247,13 +295,13 @@ class TopicController < ApplicationController
|
|
247
295
|
# if exist @topic, target is @topic, else use Topic
|
248
296
|
seven_ability_check(
|
249
297
|
[:@topic, Topic], # default targets
|
250
|
-
my_action1: {ability: :custom_ability}, # use default targets
|
251
|
-
my_action2: {ability: :custom_ability, target: [:@my_target]}
|
298
|
+
my_action1: {ability: :custom_ability}, # check :custom_ability and use default targets
|
299
|
+
my_action2: {ability: :custom_ability, target: [:@my_target]} # check :custom_ability and use :@my_target
|
252
300
|
)
|
253
301
|
# or
|
254
302
|
# seven_ability_check(
|
255
|
-
# index: {ability: read_my_ability, target: SuperTopic},
|
256
|
-
# my_action1: {ability: :custom_ability1},
|
303
|
+
# index: {ability: :read_my_ability, target: SuperTopic},
|
304
|
+
# my_action1: {ability: :custom_ability1},
|
257
305
|
# my_action2: {ability: :custom_ability2, target: [:@my_target]}
|
258
306
|
# )
|
259
307
|
|
@@ -275,7 +323,7 @@ class TopicController < ApplicationController
|
|
275
323
|
end
|
276
324
|
```
|
277
325
|
|
278
|
-
|
326
|
+
Use a customize resource name, we will get ability according to this suffix
|
279
327
|
|
280
328
|
```
|
281
329
|
class TopicController < ApplicationController
|
@@ -296,7 +344,7 @@ class TopicController < ApplicationController
|
|
296
344
|
# create: create_comment of Topic
|
297
345
|
# edit: edit_comment of @topic
|
298
346
|
# update: edit_comment of @topic
|
299
|
-
#
|
347
|
+
# destroy: delete_comment of @topic
|
300
348
|
|
301
349
|
|
302
350
|
private
|
@@ -308,11 +356,12 @@ end
|
|
308
356
|
```
|
309
357
|
|
310
358
|
|
311
|
-
|
359
|
+
Manually check, don't call `ability_check_callback`
|
312
360
|
|
313
361
|
```
|
314
362
|
class TopicController < ApplicationController
|
315
363
|
before_action :find_topic
|
364
|
+
skip_before_action :seven_ability_check_filter
|
316
365
|
|
317
366
|
def my_action1
|
318
367
|
raise 'no permission' unless can?(:read_something, @topic)
|
@@ -328,6 +377,75 @@ class TopicController < ApplicationController
|
|
328
377
|
end
|
329
378
|
```
|
330
379
|
|
380
|
+
Skip some actions
|
381
|
+
|
382
|
+
```
|
383
|
+
class TopicController < ApplicationController
|
384
|
+
before_action :find_topic
|
385
|
+
skip_before_filter :seven_ability_check_filter, only: :index
|
386
|
+
|
387
|
+
def index
|
388
|
+
if page_no > 1
|
389
|
+
ability_check_callback(can?(:read_something, @topic), :read_something, @topic)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
|
394
|
+
private
|
395
|
+
|
396
|
+
def find_topic
|
397
|
+
@topic = Topic.find(params[:id])
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
402
|
+
## Dynamic abilities
|
403
|
+
|
404
|
+
### Store
|
405
|
+
|
406
|
+
```
|
407
|
+
manager = Seven::Manager.new # read/write dynamic abilities from memory store
|
408
|
+
# or
|
409
|
+
manager = Seven::Manager.new(store: {redis: Redis.current}) # read/write dynamic abilities from Redis
|
410
|
+
# or
|
411
|
+
manager = Seven::Manager.new(store: MyStore.new) # read/write from your store, Seven just access MyStore#list methods
|
412
|
+
```
|
413
|
+
|
414
|
+
### Create your store
|
415
|
+
|
416
|
+
```
|
417
|
+
# columns: user_id: integer, ability: string, status: boolean
|
418
|
+
class Ability < ActiveRecord::Base
|
419
|
+
def self.list(user)
|
420
|
+
where(user_id: user.id).each_with_object({}) do |record, result|
|
421
|
+
result[record.ability.to_sym] = record.status # true or false
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
Seven::Manager.new(store: Ability)
|
427
|
+
|
428
|
+
# then, you can add some abilities for a user:
|
429
|
+
Ability.create(user: user, ability: :read_user, status: true)
|
430
|
+
```
|
431
|
+
|
432
|
+
|
433
|
+
### Define some dynamic rules for system store
|
434
|
+
|
435
|
+
```
|
436
|
+
$abilities_manager.store.add(user.id, :edit_user, true)
|
437
|
+
$abilities_manager.store.add(user.id, :create_user, false)
|
438
|
+
$abilities_manager.store.list(user.id) # {edit_user: true, create_user: false}
|
439
|
+
|
440
|
+
$abilities_manager.can?(user, :create_user, nil) # false
|
441
|
+
$abilities_manager.can?(user, :edit_user, nil) # true
|
442
|
+
|
443
|
+
$abilities_manager.store.del(user.id, :edit_user)
|
444
|
+
$abilities_manager.store.list(user.id) # {create_user: false}
|
445
|
+
```
|
446
|
+
|
447
|
+
|
448
|
+
|
331
449
|
## RSpec Testing
|
332
450
|
|
333
451
|
in `spec/rails_helper.rb` or `spec/spec_helper.rb`
|
@@ -336,7 +454,7 @@ in `spec/rails_helper.rb` or `spec/spec_helper.rb`
|
|
336
454
|
require 'seven/rspec'
|
337
455
|
```
|
338
456
|
|
339
|
-
Write abilities testing
|
457
|
+
Write some abilities testing
|
340
458
|
|
341
459
|
```
|
342
460
|
RSpec.describe UserAbilities do
|
@@ -352,10 +470,11 @@ end
|
|
352
470
|
```
|
353
471
|
|
354
472
|
|
473
|
+
|
474
|
+
|
355
475
|
## TODO
|
356
476
|
|
357
|
-
* [
|
358
|
-
* [ ] Dynamic rule
|
477
|
+
* [ ] Dynamic rule for a record
|
359
478
|
|
360
479
|
|
361
480
|
## Development
|
@@ -366,7 +485,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
366
485
|
|
367
486
|
## Contributing
|
368
487
|
|
369
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
488
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/xiejiangzhi/seven. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
370
489
|
|
371
490
|
|
372
491
|
## License
|
data/lib/seven/abilities.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Seven
|
2
2
|
module Abilities
|
3
3
|
class << self
|
4
|
-
def
|
4
|
+
def wrap_proc(rule_proc)
|
5
5
|
return unless rule_proc
|
6
6
|
|
7
7
|
Class.new do
|
@@ -41,8 +41,8 @@ module Seven
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def cannot(*some_abilities)
|
44
|
-
|
45
|
-
@abilities.delete_if {|ability|
|
44
|
+
symtax_abilities = some_abilities.map(&:to_sym)
|
45
|
+
@abilities.delete_if {|ability| symtax_abilities.include?(ability) }
|
46
46
|
end
|
47
47
|
|
48
48
|
|
data/lib/seven/manager.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
module Seven
|
2
2
|
class Manager
|
3
|
-
attr_reader :rules
|
3
|
+
attr_reader :rules, :store
|
4
4
|
|
5
|
-
|
5
|
+
# Params:
|
6
|
+
# store: hash or your store, the store requires get(user_id) and set(user_id, ability, status) methods
|
7
|
+
# get(user_id) should return a hash of abilities {${ability}: ${true || false}}
|
8
|
+
def initialize(store: {})
|
6
9
|
@rules = []
|
10
|
+
@store = fetch_store(store)
|
7
11
|
end
|
8
12
|
|
9
13
|
def define_rules(matcher, rule_class = nil, &rule_proc)
|
10
|
-
rule_class ||= Seven::Abilities.
|
14
|
+
rule_class ||= Seven::Abilities.wrap_proc(rule_proc)
|
11
15
|
|
12
16
|
if valid_rule_class?(rule_class)
|
13
17
|
@rules << [matcher, rule_class]
|
@@ -25,7 +29,13 @@ module Seven
|
|
25
29
|
# [A, B, Object].min # => B
|
26
30
|
# find last class
|
27
31
|
rule_class = matched_rules.min_by(&:first).last
|
28
|
-
rule_class.new(current_user, target).abilities
|
32
|
+
abilities = rule_class.new(current_user, target).abilities
|
33
|
+
|
34
|
+
# dynamic abilities
|
35
|
+
store.list(current_user).each do |new_ability, is_allowed|
|
36
|
+
is_allowed ? (abilities << new_ability) : abilities.delete(new_ability)
|
37
|
+
end
|
38
|
+
abilities.include?(ability.to_sym)
|
29
39
|
end
|
30
40
|
|
31
41
|
|
@@ -35,6 +45,24 @@ module Seven
|
|
35
45
|
return false unless rule_class && rule_class.is_a?(Class)
|
36
46
|
rule_class.included_modules.include?(Seven::Abilities)
|
37
47
|
end
|
48
|
+
|
49
|
+
def fetch_store(store_options)
|
50
|
+
unless store_options.is_a?(Hash) || store_options.nil?
|
51
|
+
if store_options.respond_to?(:list)
|
52
|
+
return store_options
|
53
|
+
else
|
54
|
+
raise "Invalid store: #{store_options.inspect}, a store should defined #list method"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
opts = (store_options || {}).symbolize_keys
|
59
|
+
|
60
|
+
if opts[:redis]
|
61
|
+
RedisStore.new(opts)
|
62
|
+
else
|
63
|
+
MemoryStore.new
|
64
|
+
end
|
65
|
+
end
|
38
66
|
end
|
39
67
|
end
|
40
68
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Seven::MemoryStore
|
2
|
+
def initialize
|
3
|
+
@data = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def set(user, ability, allowed)
|
7
|
+
(@data[user.id.to_s] ||= {}).merge!(ability.to_s.to_sym => !!allowed)
|
8
|
+
end
|
9
|
+
|
10
|
+
def del(user, ability)
|
11
|
+
(@data[user.id.to_s] ||= {}).delete(ability.to_s.to_sym)
|
12
|
+
end
|
13
|
+
|
14
|
+
def list(user)
|
15
|
+
@data[user.id.to_s] || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear(user)
|
19
|
+
@data.delete(user.id.to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_all!
|
23
|
+
@data.clear
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
class Seven::RedisStore
|
4
|
+
def initialize(redis_opts)
|
5
|
+
opts = redis_opts.symbolize_keys
|
6
|
+
@redis = opts[:redis]
|
7
|
+
end
|
8
|
+
|
9
|
+
def set(user, ability, allowed)
|
10
|
+
@redis.hset(get_user_key(user.id), ability, allowed ? '1' : '0')
|
11
|
+
end
|
12
|
+
|
13
|
+
def del(user, ability)
|
14
|
+
@redis.hdel(get_user_key(user.id), ability)
|
15
|
+
end
|
16
|
+
|
17
|
+
def list(user)
|
18
|
+
@redis.hgetall(get_user_key(user.id)).symbolize_keys.tap do |abilities|
|
19
|
+
abilities.each { |k, v| abilities[k] = v == '1' ? true : false }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear(user)
|
24
|
+
@redis.del(get_user_key(user.id))
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_all!
|
28
|
+
@redis.eval(
|
29
|
+
"local keys = redis.call('keys', 'seven_abilities/*')\n" +
|
30
|
+
"for i = 1, #keys,5000 do\n" +
|
31
|
+
" redis.call('del', unpack(keys, i, math.min(i+4999, #keys)))\n" +
|
32
|
+
"end\n" +
|
33
|
+
"return #keys"
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def get_user_key(user_id)
|
40
|
+
"seven_abilities/#{user_id}"
|
41
|
+
end
|
42
|
+
end
|
data/lib/seven/version.rb
CHANGED
data/lib/seven.rb
CHANGED
data/seven.gemspec
CHANGED
@@ -20,9 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
-
spec.add_development_dependency "
|
24
|
-
spec.add_development_dependency "
|
25
|
-
|
23
|
+
spec.add_development_dependency "rspec", '~> 3.7.0'
|
24
|
+
spec.add_development_dependency "pry", '>= 0.10'
|
25
|
+
|
26
|
+
spec.add_development_dependency "redis", '>= 3.0'
|
26
27
|
|
27
28
|
spec.add_dependency "activesupport"
|
28
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sevencan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- jiangzhi.xie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,47 +25,47 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.10'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 3.7.0
|
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: 3.7.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
47
|
+
version: '0.10'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
54
|
+
version: '0.10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: redis
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
61
|
+
version: '3.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
68
|
+
version: '3.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: activesupport
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- ".gitignore"
|
91
91
|
- ".rspec"
|
92
92
|
- ".ruby-version"
|
93
|
+
- ".travis.yml"
|
93
94
|
- CODE_OF_CONDUCT.md
|
94
95
|
- Gemfile
|
95
96
|
- LICENSE.txt
|
@@ -97,12 +98,19 @@ files:
|
|
97
98
|
- Rakefile
|
98
99
|
- bin/console
|
99
100
|
- bin/setup
|
101
|
+
- gemfiles/activesupport-4.0
|
102
|
+
- gemfiles/activesupport-4.1
|
103
|
+
- gemfiles/activesupport-4.2
|
104
|
+
- gemfiles/activesupport-5.0
|
105
|
+
- gemfiles/activesupport-5.1
|
100
106
|
- lib/seven.rb
|
101
107
|
- lib/seven/abilities.rb
|
102
108
|
- lib/seven/error.rb
|
103
109
|
- lib/seven/manager.rb
|
110
|
+
- lib/seven/memory_store.rb
|
104
111
|
- lib/seven/rails.rb
|
105
112
|
- lib/seven/rails/controller_helpers.rb
|
113
|
+
- lib/seven/redis_store.rb
|
106
114
|
- lib/seven/rspec.rb
|
107
115
|
- lib/seven/version.rb
|
108
116
|
- lib/sevencan.rb
|