sevencan 0.1.8 → 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 +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
|
+
[](https://travis-ci.org/xiejiangzhi/seven)
|
|
6
|
+
[](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
|