verifica 1.0.1 → 1.0.2
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 +27 -21
- data/lib/verifica/authorizer.rb +31 -15
- data/lib/verifica/version.rb +1 -1
- data/lib/verifica.rb +19 -13
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f38a13446f6e2b3eb7e118a1d97d5369aca85116a3bde44d7dd1f07bfe013f7a
|
4
|
+
data.tar.gz: ae82a49612de5e13ed32992a13c34a3af57e50b18772c0651194459b5a187010
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fcaee3d318dbada1fe7f4c135bbd38219b8031126e1e0d29b5c125a15f1867e8305fb4ec5ca91a2b4eddc4040ad49d24af9e79fb6a36ddab115f2d50db70d5fe
|
7
|
+
data.tar.gz: f0a4392ff578a8444b5f922ae7f3844bd14126ceb5570bf2e9ded02463a3ff63ec69d68b248e5b4241dfa10db4ab537b6fd35b2073e1d4d070f76d74f2c6e896
|
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.2] - 2023-10-25
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Make `Authorizer#authorization_result` public for even more usage flexibility
|
15
|
+
|
10
16
|
## [1.0.1] - 2023-04-15
|
11
17
|
|
12
18
|
### Added
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ for any user and resource, regardless of how complex the authorization rules are
|
|
20
20
|
|
21
21
|
*Note: Verifica is a new open-source gem, so you may wonder if it's reliable. Internally,
|
22
22
|
this solution has been battle-tested in several B2B products, including one with over 15M database records.
|
23
|
-
But anyway
|
23
|
+
But DYOR anyway.*
|
24
24
|
|
25
25
|
## Why Verifica? Isn't Pundit or CanCanCan enough?
|
26
26
|
|
@@ -38,7 +38,7 @@ In the [Real-world example with Rails](#real-world-example-with-rails) you can s
|
|
38
38
|
## Basic example
|
39
39
|
|
40
40
|
```ruby
|
41
|
-
require
|
41
|
+
require "verifica"
|
42
42
|
|
43
43
|
User = Struct.new(:id, :role, keyword_init: true) do
|
44
44
|
# Verifica expects each security subject to respond to #subject_id, #subject_type, and #subject_sids
|
@@ -78,20 +78,26 @@ superuser = User.new(id: 777, role: "root")
|
|
78
78
|
video_author = User.new(id: 1000, role: "user")
|
79
79
|
other_user = User.new(id: 2000, role: "user")
|
80
80
|
|
81
|
-
authorizer.authorized?(superuser, private_video, :delete)
|
82
|
-
# true
|
83
|
-
|
84
|
-
authorizer.authorized?(
|
85
|
-
# true
|
86
|
-
|
87
|
-
authorizer.authorized?(other_user, private_video, :read)
|
88
|
-
# false
|
81
|
+
authorizer.authorized?(superuser, private_video, :delete) # => true
|
82
|
+
authorizer.authorized?(video_author, private_video, :delete) # => true
|
83
|
+
authorizer.authorized?(other_user, private_video, :read) # => false
|
84
|
+
authorizer.authorized?(other_user, public_video, :comment) # => true
|
89
85
|
|
90
|
-
|
91
|
-
#
|
86
|
+
begin
|
87
|
+
# raises Verifica::AuthorizationError: Authorization FAILURE. Subject 'user' id='2000'. Resource 'video' id='1'. Action 'write'
|
88
|
+
authorizer.authorize(other_user, public_video, :write)
|
89
|
+
rescue Verifica::AuthorizationError => e
|
90
|
+
e.explain # => Long-form explanation of why action is not authorized, your debugging friend
|
91
|
+
end
|
92
92
|
|
93
|
-
|
94
|
-
|
93
|
+
# #authorization_result returns a special object with a bunch of useful info
|
94
|
+
auth_result = authorizer.authorization_result(superuser, private_video, :delete)
|
95
|
+
auth_result.success? # => true
|
96
|
+
auth_result.subject_id # => 777
|
97
|
+
auth_result.resource_type # => :video
|
98
|
+
auth_result.action # => :delete
|
99
|
+
auth_result.allowed_actions # => [:read, :write, :delete, :comment]
|
100
|
+
auth_result.explain # => Long-form explanation of why action is authorized
|
95
101
|
```
|
96
102
|
|
97
103
|
## Installation
|
@@ -122,11 +128,11 @@ class User
|
|
122
128
|
def subject_id
|
123
129
|
123
|
124
130
|
end
|
125
|
-
|
131
|
+
|
126
132
|
def subject_type
|
127
133
|
:user
|
128
134
|
end
|
129
|
-
|
135
|
+
|
130
136
|
def subject_sids
|
131
137
|
["root"] # see Security Identifier section below to understand what is this for
|
132
138
|
end
|
@@ -145,7 +151,7 @@ class Post
|
|
145
151
|
def resource_id
|
146
152
|
1
|
147
153
|
end
|
148
|
-
|
154
|
+
|
149
155
|
def resource_type
|
150
156
|
:post
|
151
157
|
end
|
@@ -282,7 +288,7 @@ class VideoAclProvider
|
|
282
288
|
author_org = video.author.organization
|
283
289
|
allowed_countries = author_org&.allow_countries || ds.allow_countries
|
284
290
|
denied_countries = author_org&.deny_countries || ds.deny_countries
|
285
|
-
|
291
|
+
|
286
292
|
# ...and 30 more lines to handle all our requirements
|
287
293
|
end
|
288
294
|
end
|
@@ -365,7 +371,7 @@ class Video < ApplicationRecord
|
|
365
371
|
before_save :update_read_acl
|
366
372
|
|
367
373
|
def resource_type = :video
|
368
|
-
|
374
|
+
|
369
375
|
def update_read_acl
|
370
376
|
acl = AUTHORIZER.resource_acl(self)
|
371
377
|
self.read_allow_sids = acl.allowed_sids(:read)
|
@@ -391,10 +397,10 @@ class VideosController
|
|
391
397
|
.order(:name)
|
392
398
|
.limit(50)
|
393
399
|
end
|
394
|
-
|
400
|
+
|
395
401
|
def show
|
396
402
|
@video = Video.find(params[:id])
|
397
|
-
|
403
|
+
|
398
404
|
# upon successful authorization helper object is returned with a bunch of useful info
|
399
405
|
auth_result = AUTHORIZER.authorize(current_user, @video, :read)
|
400
406
|
|
data/lib/verifica/authorizer.rb
CHANGED
@@ -73,6 +73,11 @@ module Verifica
|
|
73
73
|
|
74
74
|
# The same as {#authorize} but returns true/false instead of rising an exception
|
75
75
|
#
|
76
|
+
# @param subject (see #authorize)
|
77
|
+
# @param resource (see #authorize)
|
78
|
+
# @param action (see #authorize)
|
79
|
+
# @param context (see #authorize)
|
80
|
+
#
|
76
81
|
# @return [Boolean] true if +action+ on +resource+ is authorized for +subject+
|
77
82
|
# @raise [Error] if +resource.resource_type+ isn't registered in +self+
|
78
83
|
#
|
@@ -81,9 +86,31 @@ module Verifica
|
|
81
86
|
authorization_result(subject, resource, action, **context).success?
|
82
87
|
end
|
83
88
|
|
84
|
-
#
|
85
|
-
#
|
86
|
-
# @param
|
89
|
+
# The same as {#authorize} but returns a special result object instead of rising an exception
|
90
|
+
#
|
91
|
+
# @param subject (see #authorize)
|
92
|
+
# @param resource (see #authorize)
|
93
|
+
# @param action (see #authorize)
|
94
|
+
# @param context (see #authorize)
|
95
|
+
#
|
96
|
+
# @return [AuthorizationResult] authorization result with all details
|
97
|
+
# @raise [Error] if +resource.resource_type+ isn't registered in +self+
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
def authorization_result(subject, resource, action, **context)
|
101
|
+
action = action.to_sym
|
102
|
+
possible_actions = config_by_resource(resource).possible_actions
|
103
|
+
unless possible_actions.include?(action)
|
104
|
+
raise Error, "'#{action}' action is not registered as possible for '#{resource.resource_type}' resource"
|
105
|
+
end
|
106
|
+
|
107
|
+
acl = resource_acl(resource, **context)
|
108
|
+
AuthorizationResult.new(subject, resource, action, acl, **context)
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param subject (see #authorize)
|
112
|
+
# @param resource (see #authorize)
|
113
|
+
# @param context (see #authorize)
|
87
114
|
#
|
88
115
|
# @return [Array<Symbol>] array of actions allowed for +subject+ or empty array if none
|
89
116
|
# @raise [Error] if +resource.resource_type+ isn't registered in +self+
|
@@ -128,7 +155,7 @@ module Verifica
|
|
128
155
|
@resources.key?(resource_type.to_sym)
|
129
156
|
end
|
130
157
|
|
131
|
-
# @param resource
|
158
|
+
# @param resource (see #authorize)
|
132
159
|
# @param context [Hash] arbitrary keyword arguments to forward to +acl_provider.call+
|
133
160
|
#
|
134
161
|
# @return [Acl] Access Control List for +resource+
|
@@ -173,16 +200,5 @@ module Verifica
|
|
173
200
|
|
174
201
|
resource_config(type)
|
175
202
|
end
|
176
|
-
|
177
|
-
private def authorization_result(subject, resource, action, **context)
|
178
|
-
action = action.to_sym
|
179
|
-
possible_actions = config_by_resource(resource).possible_actions
|
180
|
-
unless possible_actions.include?(action)
|
181
|
-
raise Error, "'#{action}' action is not registered as possible for '#{resource.resource_type}' resource"
|
182
|
-
end
|
183
|
-
|
184
|
-
acl = resource_acl(resource, **context)
|
185
|
-
AuthorizationResult.new(subject, resource, action, acl, **context)
|
186
|
-
end
|
187
203
|
end
|
188
204
|
end
|
data/lib/verifica/version.rb
CHANGED
data/lib/verifica.rb
CHANGED
@@ -20,7 +20,7 @@ require_relative "verifica/version"
|
|
20
20
|
# (who can do what for any given resource) and execution (can +current_user+ delete this post?).
|
21
21
|
#
|
22
22
|
# @example
|
23
|
-
# require
|
23
|
+
# require "verifica"
|
24
24
|
#
|
25
25
|
# User = Struct.new(:id, :role, keyword_init: true) do
|
26
26
|
# # Verifica expects each security subject to respond to #subject_id, #subject_type, and #subject_sids
|
@@ -60,20 +60,26 @@ require_relative "verifica/version"
|
|
60
60
|
# video_author = User.new(id: 1000, role: "user")
|
61
61
|
# other_user = User.new(id: 2000, role: "user")
|
62
62
|
#
|
63
|
-
# authorizer.authorized?(superuser, private_video, :delete)
|
64
|
-
# # true
|
63
|
+
# authorizer.authorized?(superuser, private_video, :delete) # => true
|
64
|
+
# authorizer.authorized?(video_author, private_video, :delete) # => true
|
65
|
+
# authorizer.authorized?(other_user, private_video, :read) # => false
|
66
|
+
# authorizer.authorized?(other_user, public_video, :comment) # => true
|
65
67
|
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# authorizer.authorized?(other_user, public_video, :comment)
|
73
|
-
# # true
|
68
|
+
# begin
|
69
|
+
# # raises Verifica::AuthorizationError: Authorization FAILURE. Subject 'user' id='2000'. Resource 'video' id='1'. Action 'write'
|
70
|
+
# authorizer.authorize(other_user, public_video, :write)
|
71
|
+
# rescue Verifica::AuthorizationError => e
|
72
|
+
# e.explain # => Long-form explanation of why action is not authorized, your debugging friend
|
73
|
+
# end
|
74
74
|
#
|
75
|
-
#
|
76
|
-
#
|
75
|
+
# # #authorization_result returns a special object with a bunch of useful info
|
76
|
+
# auth_result = authorizer.authorization_result(superuser, private_video, :delete)
|
77
|
+
# auth_result.success? # => true
|
78
|
+
# auth_result.subject_id # => 777
|
79
|
+
# auth_result.resource_type # => :video
|
80
|
+
# auth_result.action # => :delete
|
81
|
+
# auth_result.allowed_actions # => [:read, :write, :delete, :comment]
|
82
|
+
# auth_result.explain # => Long-form explanation of why action is authorized
|
77
83
|
#
|
78
84
|
# @api public
|
79
85
|
module Verifica
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maxim Gurin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -120,7 +120,7 @@ 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.
|
123
|
+
rubygems_version: 3.4.19
|
124
124
|
signing_key:
|
125
125
|
specification_version: 4
|
126
126
|
summary: The most scalable authorization solution for Ruby
|