verifica 1.0.0 → 1.0.1
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/CHANGELOG.md +6 -0
- data/README.md +17 -13
- data/lib/verifica/acl.rb +1 -2
- data/lib/verifica/authorizer.rb +1 -0
- data/lib/verifica/sid.rb +61 -0
- data/lib/verifica/version.rb +1 -1
- data/lib/verifica.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccea0b2769fa053006c2ec3d0b4cd0d3cc65a3a4b4375ce390dabe3a093f490d
|
4
|
+
data.tar.gz: 0bb0dd63b30f08a1df1af771671312182cde2c159613094f9fd0c664201b6e24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c3bfab0ce9ccd36cd91baadcc4454e462c9a5d098c6295d3073040d3291030e516d7ae90d200b55b84d4cafd51cd0118c51a1e5a5e0ee075d54e18528033d01
|
7
|
+
data.tar.gz: 6f6011eaec5b7f8a1d810233350a8a68cb7fdf7cf86d8bdc069d51478826ce847461c9d53e5c5f7e83922ac956cb3c1546fd957cb973c5dfee1a71cbc7258726
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [1.0.1] - 2023-04-15
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- `Sid#country_sid` and `Sid#group_sid` helper methods
|
15
|
+
|
10
16
|
## [1.0.0] - 2023-01-19
|
11
17
|
|
12
18
|
Initial public release
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
[](https://badge.fury.io/rb/verifica)
|
1
2
|
[](https://github.com/maximgurin/verifica/actions/workflows/ci.yml)
|
3
|
+
[](http://rubydoc.info/github/maximgurin/verifica)
|
2
4
|
[](https://www.codacy.com/gh/maximgurin/verifica/dashboard?utm_source=github.com&utm_medium=referral&utm_content=maximgurin/verifica&utm_campaign=Badge_Grade)
|
3
5
|
[](https://www.codacy.com/gh/maximgurin/verifica/dashboard?utm_source=github.com&utm_medium=referral&utm_content=maximgurin/verifica&utm_campaign=Badge_Coverage)
|
4
6
|

|
@@ -22,7 +24,7 @@ But anyway, trust nothing. DYOR.*
|
|
22
24
|
|
23
25
|
## Why Verifica? Isn't Pundit or CanCanCan enough?
|
24
26
|
|
25
|
-
Let's say you working on a video platform application:
|
27
|
+
Let's say you are working on a video platform application:
|
26
28
|
|
27
29
|
- You have 10M videos in the database
|
28
30
|
- 7 types of user roles
|
@@ -70,7 +72,7 @@ authorizer = Verifica.authorizer do |config|
|
|
70
72
|
end
|
71
73
|
|
72
74
|
public_video = Video.new(id: 1, author_id: 1000, public: true)
|
73
|
-
private_video = Video.new(id: 2, author_id: 1000, public:
|
75
|
+
private_video = Video.new(id: 2, author_id: 1000, public: false)
|
74
76
|
|
75
77
|
superuser = User.new(id: 777, role: "root")
|
76
78
|
video_author = User.new(id: 1000, role: "user")
|
@@ -184,9 +186,9 @@ end
|
|
184
186
|
|
185
187
|
video_acl.to_a
|
186
188
|
# =>
|
187
|
-
# [#<Verifica::Ace:0x00007fab1955dd60 @action=:
|
188
|
-
# #<Verifica::Ace:0x00007fab1955dd10 @action=:comment, @allow=true, @sid="authenticated">,
|
189
|
-
# #<Verifica::Ace:0x00007fab1955dc48 @action=:
|
189
|
+
# [#<Verifica::Ace:0x00007fab1955dd60 @action=:read, @allow=true, @sid="authenticated">,
|
190
|
+
# #<Verifica::Ace:0x00007fab1955dd10 @action=:comment, @allow=true, @sid="authenticated">,
|
191
|
+
# #<Verifica::Ace:0x00007fab1955dc48 @action=:read, @allow=false, @sid="country:US">]
|
190
192
|
```
|
191
193
|
|
192
194
|
### AclProvider
|
@@ -210,8 +212,8 @@ end
|
|
210
212
|
### Authorizer
|
211
213
|
|
212
214
|
And finally, Authorizer, the heart of Verifica. It couples all concepts above into an isolated container with no global state.
|
213
|
-
Each Authorizer has a list of resource types registered with their companion AclProviders
|
214
|
-
|
215
|
+
Each Authorizer has a list of resource types registered with their companion AclProviders and
|
216
|
+
several methods to check the Subject's rights to perform a specific action on a given resource.
|
215
217
|
|
216
218
|
Check the [Basic example](#basic-example) above to see how it all plays together.
|
217
219
|
|
@@ -318,11 +320,11 @@ class User < ApplicationRecord
|
|
318
320
|
when "moderator"
|
319
321
|
[user_sid(id), role_sid("moderator")]
|
320
322
|
when "user"
|
321
|
-
sids = [authenticated_sid, user_sid(id),
|
323
|
+
sids = [authenticated_sid, user_sid(id), country_sid(country)]
|
322
324
|
organization_id.try { |org_id| sids.push(organization_sid(org_id)) }
|
323
325
|
sids
|
324
326
|
when "organization_admin"
|
325
|
-
sids = [authenticated_sid, user_sid(id),
|
327
|
+
sids = [authenticated_sid, user_sid(id), country_sid(country)]
|
326
328
|
sids.push(organization_sid(organization_id))
|
327
329
|
sids.push(role_sid("organization_admin:#{organization_id}"))
|
328
330
|
else
|
@@ -354,7 +356,7 @@ end
|
|
354
356
|
```
|
355
357
|
|
356
358
|
```ruby
|
357
|
-
# app/models/
|
359
|
+
# app/models/video.rb
|
358
360
|
|
359
361
|
class Video < ApplicationRecord
|
360
362
|
attr_accessor :allowed_actions
|
@@ -383,7 +385,11 @@ end
|
|
383
385
|
|
384
386
|
class VideosController
|
385
387
|
def index
|
386
|
-
@videos = Video
|
388
|
+
@videos = Video
|
389
|
+
.includes(:distribution_setting, author: [:organization])
|
390
|
+
.available_for(current_user)
|
391
|
+
.order(:name)
|
392
|
+
.limit(50)
|
387
393
|
end
|
388
394
|
|
389
395
|
def show
|
@@ -415,8 +421,6 @@ run a background job to find all affected videos and update `read_allow_sids`, `
|
|
415
421
|
Same applies to Distribution Settings and other dependencies.
|
416
422
|
- **Rules change handling.** If implementation of `VideoAclProvider` changed you need to run a background job
|
417
423
|
to update `read_allow_sids`, `read_deny_sids` columns for all videos.
|
418
|
-
- **Cache, N+1 problem.** `VideoAclProvider` retrieves a chain of records associated with each video which leads to
|
419
|
-
N+1 problem in a naive implementation.
|
420
424
|
|
421
425
|
See also:
|
422
426
|
|
data/lib/verifica/acl.rb
CHANGED
@@ -54,7 +54,6 @@ module Verifica
|
|
54
54
|
|
55
55
|
allow_deny[:allowed_sids].freeze
|
56
56
|
allow_deny[:denied_sids].freeze
|
57
|
-
allow_deny.freeze
|
58
57
|
end
|
59
58
|
|
60
59
|
@allowed_actions.freeze
|
@@ -154,7 +153,7 @@ module Verifica
|
|
154
153
|
|
155
154
|
# @example
|
156
155
|
# acl = Verifica::Acl.build { |acl| acl.allow "root", [:read, :write] }
|
157
|
-
# acl.to_a.map(
|
156
|
+
# acl.to_a.map(&:to_h)
|
158
157
|
# # => [{:sid=>"root", :action=>:read, :allow=>true}, {:sid=>"root", :action=>:write, :allow=>true}]
|
159
158
|
#
|
160
159
|
# @return [Array<Ace>] a new array representing +self+
|
data/lib/verifica/authorizer.rb
CHANGED
@@ -141,6 +141,7 @@ module Verifica
|
|
141
141
|
def resource_acl(resource, **context)
|
142
142
|
config = config_by_resource(resource)
|
143
143
|
acl = config.acl_provider.call(resource, **context)
|
144
|
+
# trade-off flexibility to increase robustness here by requiring a specific type
|
144
145
|
unless acl.is_a?(Verifica::Acl)
|
145
146
|
type = resource.resource_type
|
146
147
|
raise Error, "'#{type}' resource acl_provider should respond to #call with Acl object but got '#{acl.class}'"
|
data/lib/verifica/sid.rb
CHANGED
@@ -211,5 +211,66 @@ module Verifica
|
|
211
211
|
|
212
212
|
"org:#{organization_id}".freeze
|
213
213
|
end
|
214
|
+
|
215
|
+
# Security Identifier of the subject who is a member of the group with given +group_id+
|
216
|
+
#
|
217
|
+
# @note (see #user_sid)
|
218
|
+
#
|
219
|
+
# @example
|
220
|
+
# class PostAclProvider
|
221
|
+
# include Verifica::Sid
|
222
|
+
#
|
223
|
+
# def call(post, **)
|
224
|
+
# Verifica::Acl.build do |acl|
|
225
|
+
# post.editor_groups.each do |group_id|
|
226
|
+
# acl.allow group_sid(group_id), [:edit, :delete]
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# # ...
|
230
|
+
# end
|
231
|
+
# end
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# @return [String]
|
235
|
+
#
|
236
|
+
# @api public
|
237
|
+
def group_sid(group_id)
|
238
|
+
if group_id.nil?
|
239
|
+
raise ArgumentError, "Nil 'group_id' is unsafe. Use empty string if you absolutely need this behavior"
|
240
|
+
end
|
241
|
+
|
242
|
+
"group:#{group_id}".freeze
|
243
|
+
end
|
244
|
+
|
245
|
+
# Security Identifier of the subject whose country is the country with given +country_id+
|
246
|
+
#
|
247
|
+
# @note (see #user_sid)
|
248
|
+
#
|
249
|
+
# @example
|
250
|
+
# class PostAclProvider
|
251
|
+
# include Verifica::Sid
|
252
|
+
#
|
253
|
+
# def call(post, **)
|
254
|
+
# Verifica::Acl.build do |acl|
|
255
|
+
# acl.allow authenticated_sid, [:read, :comment]
|
256
|
+
# post.banned_countries.each do |country_id|
|
257
|
+
# acl.deny country_sid(country_id), [:read, :comment]
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# # ...
|
261
|
+
# end
|
262
|
+
# end
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# @return [String]
|
266
|
+
#
|
267
|
+
# @api public
|
268
|
+
def country_sid(country_id)
|
269
|
+
if country_id.nil?
|
270
|
+
raise ArgumentError, "Nil 'country_id' is unsafe. Use empty string if you absolutely need this behavior"
|
271
|
+
end
|
272
|
+
|
273
|
+
"country:#{country_id}".freeze
|
274
|
+
end
|
214
275
|
end
|
215
276
|
end
|
data/lib/verifica/version.rb
CHANGED
data/lib/verifica.rb
CHANGED
@@ -54,7 +54,7 @@ require_relative "verifica/version"
|
|
54
54
|
# end
|
55
55
|
#
|
56
56
|
# public_video = Video.new(id: 1, author_id: 1000, public: true)
|
57
|
-
# private_video = Video.new(id: 2, author_id: 1000, public:
|
57
|
+
# private_video = Video.new(id: 2, author_id: 1000, public: false)
|
58
58
|
#
|
59
59
|
# superuser = User.new(id: 777, role: "root")
|
60
60
|
# video_author = User.new(id: 1000, role: "user")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: verifica
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxim Gurin
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -105,7 +105,7 @@ metadata:
|
|
105
105
|
source_code_uri: https://github.com/maximgurin/verifica
|
106
106
|
bug_tracker_uri: https://github.com/maximgurin/verifica/issues
|
107
107
|
rubygems_mfa_required: 'true'
|
108
|
-
post_install_message:
|
108
|
+
post_install_message:
|
109
109
|
rdoc_options: []
|
110
110
|
require_paths:
|
111
111
|
- lib
|
@@ -120,8 +120,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
120
|
- !ruby/object:Gem::Version
|
121
121
|
version: '0'
|
122
122
|
requirements: []
|
123
|
-
rubygems_version: 3.4.
|
124
|
-
signing_key:
|
123
|
+
rubygems_version: 3.4.12
|
124
|
+
signing_key:
|
125
125
|
specification_version: 4
|
126
126
|
summary: The most scalable authorization solution for Ruby
|
127
127
|
test_files: []
|