accessly 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +290 -6
- data/lib/accessly.rb +1 -0
- data/lib/accessly/policy/base.rb +31 -15
- data/lib/accessly/version.rb +1 -1
- data/logo/logo.png +0 -0
- data/logo/logo.svg +8 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9abbf504e2ae963dde890f7175961694b521462b
|
4
|
+
data.tar.gz: a470d71b034a205dc6479c2e015f47117fd097f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93b7e81d5ec169e79882348b900c48a4906e7b5006891cf3a42df334261ce9be085b604a6393dd8e23ce4493489a0c79a8f5766bf96dd363b234a0eec9924874
|
7
|
+
data.tar.gz: e728bcecd705c55b36c21cd325855fabdd7a19e513eb6bf2a808bd2d8889eb97337c2e120e556cf2625341ff2d5a6720a903585e977fd58a5cf5def062c7bbe4
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
accessly (0.0
|
4
|
+
accessly (1.0.0)
|
5
5
|
activerecord (~> 5.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -115,11 +115,11 @@ PLATFORMS
|
|
115
115
|
DEPENDENCIES
|
116
116
|
accessly!
|
117
117
|
bundler (~> 1.16)
|
118
|
-
database_cleaner
|
118
|
+
database_cleaner (~> 1.5)
|
119
119
|
minitest (~> 5.0)
|
120
120
|
rails (~> 5.0)
|
121
121
|
rake (~> 10.0)
|
122
|
-
sqlite3
|
122
|
+
sqlite3 (~> 1.3)
|
123
123
|
|
124
124
|
RUBY VERSION
|
125
125
|
ruby 2.4.2p198
|
data/README.md
CHANGED
@@ -1,8 +1,27 @@
|
|
1
|
+
<img width="268" src="https://raw.githubusercontent.com/lessonly/accessly/master/logo/logo.png" alt="Accessly Logo">
|
2
|
+
|
1
3
|
# Accessly
|
2
4
|
|
3
|
-
|
5
|
+
Accessly exists out of our need to answer the following questions:
|
6
|
+
|
7
|
+
1. What can a user do or see?
|
8
|
+
2. Can a user do an arbitrary action on another object?
|
9
|
+
|
10
|
+
We were not satisfied with the available resources to answer the questions so we created Accessly!
|
11
|
+
|
12
|
+
Accessly is our opinion of access control that can broadly grant permissions to 'actors' (modeled as `users`, `groups`, `organizations`, etc)
|
13
|
+
|
14
|
+
```
|
15
|
+
Can actor1 view the content resource (/content)?
|
16
|
+
```
|
17
|
+
|
18
|
+
Our `actors` can also have permissions on other models in our application
|
19
|
+
|
20
|
+
```
|
21
|
+
Can actor1 edit a Post with id 1?
|
22
|
+
```
|
4
23
|
|
5
|
-
|
24
|
+
If you have a similar need to implement a permission scheme in your Rails app please continue reading!
|
6
25
|
|
7
26
|
## Installation
|
8
27
|
|
@@ -20,15 +39,280 @@ Or install it yourself as:
|
|
20
39
|
|
21
40
|
$ gem install accessly
|
22
41
|
|
42
|
+
Add the ActiveRecord Migrations:
|
43
|
+
|
44
|
+
$ rails g accessly:install
|
45
|
+
|
23
46
|
## Usage
|
24
47
|
|
25
|
-
|
48
|
+
You can use the Accessly gem directly to grant | revoke | check permissions. We recommend the use of 'Policies' covered in this README.
|
49
|
+
Checkout our [API docs](http://www.rubydoc.info/gems/accessly) for more info on using the API directly
|
50
|
+
|
51
|
+
We use Accessly with policies in mind to capture everything we want to know about a specific permission set. Let's take a look at some examples:
|
52
|
+
|
53
|
+
### Basic Action Policy
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class ApplicationFeaturePolicy < Accessly::Policy::Base
|
57
|
+
|
58
|
+
actions(
|
59
|
+
view_dashboard: 1,
|
60
|
+
view_super_secret_page: 2,
|
61
|
+
view_double_secret_probation_page: 3
|
62
|
+
)
|
63
|
+
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
With this policy we can `grant` permissions to a user
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
ApplicationFeaturePolicy.new(user).grant!(:view_super_secret_page)
|
71
|
+
```
|
72
|
+
|
73
|
+
In our `SuperSecretPageController`, we can check whether the user has permission to view that page with
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
ApplicationFeaturePolicy.new(user).view_super_secret_page?
|
77
|
+
# or
|
78
|
+
ApplicationFeaturePolicy.new(user).can?(:view_super_secret_page)
|
79
|
+
```
|
80
|
+
|
81
|
+
At any point in time we can revoke permissions with
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
ApplicationFeaturePolicy.new(user).revoke!(:view_super_secret_page)
|
85
|
+
```
|
86
|
+
|
87
|
+
### Basic Action on Object Policy
|
88
|
+
|
89
|
+
We can grant permissions to `actors` on other `objects` in our application with a policy like:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class UserPolicy < Accessly::Policy::Base
|
93
|
+
|
94
|
+
actions_on_objects(
|
95
|
+
view: 1,
|
96
|
+
edit: 2,
|
97
|
+
destroy: 3
|
98
|
+
)
|
99
|
+
|
100
|
+
def self.namespace
|
101
|
+
User.name
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.model_scope
|
105
|
+
User.all
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
We differentiate permissions by a `namespace` which by default is the name of your policy class. However,
|
111
|
+
it may be necessary to override the default behavior represented in the above example.
|
112
|
+
|
113
|
+
Accessly can return a relation of ids on an object for a given actor's permission grants. `Accessly::Policy::Base` requires
|
114
|
+
that you implement `self.model_scope` with an `ActiveRecord` scope so the `list` api can return an `ActiveRecord::Relation`
|
115
|
+
|
116
|
+
With this policy we can `grant` permissions for a user to do an action on another user object.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
UserPolicy.new(user).grant!(:edit, other_user)
|
120
|
+
```
|
121
|
+
|
122
|
+
In our `EditUserController`, we can check permissions
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
UserPolicy.new(user).edit?(other_user)
|
126
|
+
# or
|
127
|
+
UserPolicy.new(user).can?(:edit, other_user)
|
128
|
+
```
|
129
|
+
|
130
|
+
We can list all of the users available to edit with
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
UserPolicy.new(user).edit
|
134
|
+
# or
|
135
|
+
UserPolicy.new(user).list(:edit)
|
136
|
+
```
|
137
|
+
|
138
|
+
At any point in time we can revoke permissions with
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
UserPolicy.new(user).revoke!(:edit, other_user)
|
142
|
+
```
|
143
|
+
|
144
|
+
### Intermediate Action Policy
|
145
|
+
|
146
|
+
Let's look at a policy with a combined configuration and more customization
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class UserPolicy < Accessly::Policy::Base
|
150
|
+
|
151
|
+
actions(
|
152
|
+
view: 1,
|
153
|
+
edit_basic_info: 2,
|
154
|
+
change_role: 3,
|
155
|
+
email: 4
|
156
|
+
)
|
157
|
+
|
158
|
+
actions_on_objects(
|
159
|
+
view: 1,
|
160
|
+
edit: 2,
|
161
|
+
destroy: 3,
|
162
|
+
)
|
163
|
+
|
164
|
+
def self.namespace
|
165
|
+
User.name
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.model_scope
|
169
|
+
User.all
|
170
|
+
end
|
171
|
+
|
172
|
+
def segment_id
|
173
|
+
actor.organization_id
|
174
|
+
end
|
175
|
+
|
176
|
+
def unrestricted?
|
177
|
+
actor.admin?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
This policy combines `actions` and `actions_on_objects`, introduces Accessly's support for `segment_id`, and overrides `unrestricted?`
|
183
|
+
|
184
|
+
#### combined actions and actions_on_objects
|
185
|
+
|
186
|
+
Accessly policies can extend support for combined use of `actions` and `actions_on_objects.` You may want to broadly grant `edit_basic_info` permissions to some users. The same policy can support a limited scope of permissions where the `actor` and `object` must be defined.
|
187
|
+
|
188
|
+
#### segment_id
|
189
|
+
|
190
|
+
`segment_id` allows you to scope permission grants to a specific object id that you define. In our example the `actor` belongs to an Organization model, and we set the organization_id on each permission granted for any actor using the policy.
|
191
|
+
|
192
|
+
It provides additional efficiency on query execution, and we can broadly remove permissions if the organization is no longer in the application.
|
193
|
+
|
194
|
+
#### unrestricted?
|
195
|
+
|
196
|
+
Accessly uses `unrestricted?` to bypass permission checks. This policy shows that the actor has an `admin` designation which we do not want to model in permissions. The business logic implemented here would bypass any permission check if `unrestricted?` returns `true`. When `unrestricted?` returns `true`, then `can?` and the other permission check methods (like `edit_basic_info?` in this example) automatically return `true`, and `list` and the other list methods (like `edit` in this example) returns the `ActiveRecord::Relation` given by `self.model_scope`
|
197
|
+
|
198
|
+
### Advanced Action Policy
|
199
|
+
|
200
|
+
Let's look at a policy that overrides `action?` and `list` APIs
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
class UserPolicy < Accessly::Policy::Base
|
204
|
+
|
205
|
+
actions(
|
206
|
+
view: 1,
|
207
|
+
edit_basic_info: 2,
|
208
|
+
change_role: 3,
|
209
|
+
email: 4
|
210
|
+
)
|
211
|
+
|
212
|
+
actions_on_objects(
|
213
|
+
view: 1,
|
214
|
+
edit: 2,
|
215
|
+
destroy: 3
|
216
|
+
)
|
217
|
+
|
218
|
+
def self.namespace
|
219
|
+
User.name
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.model_scope
|
223
|
+
User.all
|
224
|
+
end
|
225
|
+
|
226
|
+
# Override the destroy permission check for an "Action on Object"
|
227
|
+
def destroy?(object)
|
228
|
+
if actor.name == "Alice"
|
229
|
+
true
|
230
|
+
else
|
231
|
+
super
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Override the view permission check for both Action only and "Action on Object"
|
236
|
+
def view?(object = nil)
|
237
|
+
if object.nil?
|
238
|
+
if actor.name == "Bob"
|
239
|
+
false
|
240
|
+
else
|
241
|
+
super
|
242
|
+
end
|
243
|
+
elsif actor.name == "Alice" && object.name == "Bob"
|
244
|
+
true
|
245
|
+
else
|
246
|
+
super
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Override the change_role check for Action only
|
251
|
+
def change_role?
|
252
|
+
false
|
253
|
+
end
|
254
|
+
|
255
|
+
# Override the list method for view permissions
|
256
|
+
def view
|
257
|
+
if actor.name == "Alice"
|
258
|
+
User.all
|
259
|
+
else
|
260
|
+
super
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
```
|
265
|
+
#### Overriding defaults
|
266
|
+
|
267
|
+
Here we provide some examples of the `Accessly::Policy::Base` overrides you can make in an application. You can override the function completely or fallback to the `Base` method. The implementation strategy is up to you!
|
268
|
+
|
269
|
+
Any call to the following functions will run the given example in the policy:
|
270
|
+
|
271
|
+
#### destroy?(object)
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
# Action on Object queries
|
275
|
+
UserPolicy.new(user).destroy?(other_user)
|
276
|
+
# or
|
277
|
+
UserPolicy.new(user).can?(:destroy, other_user)
|
278
|
+
```
|
279
|
+
|
280
|
+
#### view?(object = nil)
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
# Action queries
|
284
|
+
UserPolicy.new(user).view?
|
285
|
+
# or
|
286
|
+
UserPolicy.new(user).can?(:view)
|
287
|
+
|
288
|
+
# Action on Object queries
|
289
|
+
UserPolicy.new(user).view?(other_user)
|
290
|
+
# or
|
291
|
+
UserPolicy.new(user).can?(:view, other_user)
|
292
|
+
```
|
293
|
+
|
294
|
+
#### change_role?
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
# Action queries
|
298
|
+
UserPolicy.new(user).change_role?
|
299
|
+
# or
|
300
|
+
UserPolicy.new(user).can?(:change_role)
|
301
|
+
```
|
302
|
+
|
303
|
+
#### view
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
# List queries
|
307
|
+
UserPolicy.new(user).view
|
308
|
+
# or
|
309
|
+
UserPolicy.new(user).list(:view)
|
310
|
+
```
|
26
311
|
|
27
|
-
##
|
312
|
+
## Caching
|
28
313
|
|
29
|
-
|
314
|
+
Accessly implements some internal caching to increase the performance of permission queries. If you use the same Policy object for the same lookup twice, then the second one will lookup based on the cached result. Be mindful of caching when using `revoke!` or `grant!` calls with subsequent permission queries on the same Policy object.
|
30
315
|
|
31
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
316
|
|
33
317
|
## Contributing
|
34
318
|
|
data/lib/accessly.rb
CHANGED
data/lib/accessly/policy/base.rb
CHANGED
@@ -38,6 +38,14 @@ module Accessly
|
|
38
38
|
self.class.model_scope
|
39
39
|
end
|
40
40
|
|
41
|
+
# Specifies all the actors used in permission lookups.
|
42
|
+
# Override this method in child policy classes to specify
|
43
|
+
# other actors that the actor given in the initializer may
|
44
|
+
# inherit permissions from.
|
45
|
+
def actors
|
46
|
+
actor
|
47
|
+
end
|
48
|
+
|
41
49
|
def unrestricted?
|
42
50
|
false
|
43
51
|
end
|
@@ -46,33 +54,33 @@ module Accessly
|
|
46
54
|
nil
|
47
55
|
end
|
48
56
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
action_id = if object_id.nil?
|
53
|
-
_get_general_action_id!(action)
|
57
|
+
def can?(action, object = nil)
|
58
|
+
if object.nil?
|
59
|
+
send("#{action}?")
|
54
60
|
else
|
55
|
-
|
61
|
+
send("#{action}?", object)
|
56
62
|
end
|
63
|
+
end
|
57
64
|
|
58
|
-
|
65
|
+
def list(action)
|
66
|
+
send(action)
|
59
67
|
end
|
60
68
|
|
61
|
-
def
|
69
|
+
def grant!(action, object = nil)
|
62
70
|
object_id = _get_object_id(object)
|
71
|
+
action_id = _get_action_id(action, object_id)
|
72
|
+
grant_object.grant!(action_id, namespace, object_id)
|
73
|
+
end
|
63
74
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
_get_action_on_object_id!(action)
|
68
|
-
end
|
69
|
-
|
75
|
+
def revoke!(action, object = nil)
|
76
|
+
object_id = _get_object_id(object)
|
77
|
+
action_id = _get_action_id(action, object_id)
|
70
78
|
revoke_object.revoke!(action_id, namespace, object_id)
|
71
79
|
end
|
72
80
|
|
73
81
|
def accessly_query
|
74
82
|
@_accessly_query ||= begin
|
75
|
-
query = Accessly::Query.new(
|
83
|
+
query = Accessly::Query.new(actors)
|
76
84
|
query.on_segment(segment_id) unless segment_id.nil?
|
77
85
|
query
|
78
86
|
end
|
@@ -94,6 +102,14 @@ module Accessly
|
|
94
102
|
|
95
103
|
private
|
96
104
|
|
105
|
+
def _get_action_id(action, object_id = nil)
|
106
|
+
if object_id.nil?
|
107
|
+
_get_general_action_id!(action)
|
108
|
+
else
|
109
|
+
_get_action_on_object_id!(action)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
97
113
|
# Determines whether the caller is trying to call an action method
|
98
114
|
# in the format `action_name?`. If so, this calls that method with
|
99
115
|
# the given arguments.
|
data/lib/accessly/version.rb
CHANGED
data/logo/logo.png
ADDED
Binary file
|
data/logo/logo.svg
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 268 60">
|
3
|
+
<g fill="none" fill-rule="evenodd">
|
4
|
+
<path fill="#292933" d="M62 59c-10.6 0-19-7.5-19-18 0-11.5 8.4-19 19-19 9 0 13.6 4.9 16 10l-8 3a9.3 9.3 0 0 0-8-5c-5.5.2-9.6 4.7-10 11 .4 5.3 4.5 9.8 10 10a9.3 9.3 0 0 0 8-5l8 3c-2.3 5-7 10-16 10zm37 0c-10.6 0-19-7.5-19-18 0-11.5 8.4-19 19-19 9 0 13.6 4.9 16 10l-8 3a9.3 9.3 0 0 0-8-5c-5.5.2-9.6 4.7-10 11 .4 5.3 4.5 9.8 10 10a9.3 9.3 0 0 0 8-5l8 3c-2.3 5-7 10-16 10zm45 0h-26V23h26v8h-17v6h17v8h-17v6h17v8zm18 0c-6.6 0-11.5-2.3-15-6l5-7c2.3 3 6.1 5.1 11 5 3 .1 4.9-1.1 5-3-.1-4.7-19.9-.8-20-15 .1-5.6 5-11 14-11a20 20 0 0 1 14 5l-5 7a13.2 13.2 0 0 0-10-4c-2.6-.1-3.7 1-4 2 .3 5 20 1.7 20 15 0 7.2-5.2 12-15 12zm32 0c-6.6 0-11.5-2.3-15-6l5-7c2.3 3 6.1 5.1 11 5 3 .1 4.9-1.1 5-3-.1-4.7-19.9-.8-20-15 .1-5.6 5-11 14-11a20 20 0 0 1 14 5l-5 7a13.2 13.2 0 0 0-10-4c-2.6-.1-3.7 1-4 2 .3 5 20 1.7 20 15 0 7.2-5.2 12-15 12zm43-1h-23V23h9v27h14v8zm16 0h-8V44.4L231 23h10l8 13.6 8-13.6h10l-14 21.4V58z"/>
|
5
|
+
<path fill="#F6BF35" d="M20 60a19 19 0 1 1 0-38 19 19 0 0 1 0 38zm-5-23.3c0 1.7 1.1 3.5 3 4.8l-1.5 7a2 2 0 0 0 2 2.5h4a2 2 0 0 0 2-2.4L23 41.5c1.9-1.3 3-3 3-4.8 0-3.3-2.5-5.7-5.5-5.7S15 33.4 15 36.7z"/>
|
6
|
+
<path stroke="#F6BF35" stroke-linecap="round" stroke-width="8" d="M9 16.8C9 10.3 14.1 5 20.5 5S32 10.3 32 16.8V43"/>
|
7
|
+
</g>
|
8
|
+
</svg>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: accessly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Milam
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2018-03
|
13
|
+
date: 2018-04-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -149,6 +149,8 @@ files:
|
|
149
149
|
- lib/generators/accessly/install/install_generator.rb
|
150
150
|
- lib/generators/accessly/install/templates/db/migrate/create_permitted_action_on_objects.rb
|
151
151
|
- lib/generators/accessly/install/templates/db/migrate/create_permitted_actions.rb
|
152
|
+
- logo/logo.png
|
153
|
+
- logo/logo.svg
|
152
154
|
homepage: https://github.com/lessonly/accessly
|
153
155
|
licenses:
|
154
156
|
- MIT
|