recurso 0.5.3 → 0.6.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/README.md +25 -0
- data/lib/recurso/concerns/identity.rb +9 -0
- data/lib/recurso/config.rb +4 -1
- data/lib/recurso/models/global.rb +21 -0
- data/lib/recurso/policies/resource_policy.rb +3 -12
- data/lib/recurso/queries/relation.rb +38 -26
- data/lib/recurso/version.rb +1 -1
- data/lib/recurso.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a46321da0a26c046aa4fbfebe14061760fe414a8f82c50ec16bb018ce99b39e1
|
4
|
+
data.tar.gz: e8663c77f4aebdcee48b8582f218cfb3adacbe92a65cc0c8cc33552cce08d8a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0842b588a11c858e9f08099d6333447ee16863f11e20a68319f8f33e0644a796e5366e87fdb15f88fae1133fd93a854831d440d80316cdff4b539d539c7a37c6'
|
7
|
+
data.tar.gz: 20aa209fa69e2a2037f84f16c34a35e70492b15ea301c1c7aca541332aa08dd5584a225f6f33b2358ac481c71d75292f5b102d23609cd8878e9009ed6ab87c89
|
data/README.md
CHANGED
@@ -81,6 +81,15 @@ The second, which resources a user can access of a given relation:
|
|
81
81
|
# ^^ which squad can I view within this team?
|
82
82
|
```
|
83
83
|
|
84
|
+
Calling `resources_with_permission` directly on an identity will return all records in the database which that identity has access to.
|
85
|
+
|
86
|
+
_(NB that these classes must be whitelisted via the `global_relations` config parameter, documented below)_
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
@user.resources_with_permission(:teams)
|
90
|
+
# ^^ which teams in the the database can I view?
|
91
|
+
```
|
92
|
+
|
84
93
|
#### 4. Authorizing resources
|
85
94
|
|
86
95
|
A common use case for these permissions is to authorize actions on a certain controller action.
|
@@ -272,6 +281,22 @@ Recurso::Config.instance.identity_foreign_key = lambda do |model|
|
|
272
281
|
end
|
273
282
|
```
|
274
283
|
|
284
|
+
**global_relations**:
|
285
|
+
|
286
|
+
The names of relations you're interested in accessing globally. This expects an array of symbols, which will be constantized in order to find a class name
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
Recurso::Config.instance.global_relations = [:organizations, :teams, :squads]
|
290
|
+
```
|
291
|
+
|
292
|
+
This enables querying the `Recurso::Global` for all models of that class.
|
293
|
+
|
294
|
+
e.g.:
|
295
|
+
```ruby
|
296
|
+
# return all teams in the database which this user can view
|
297
|
+
user.resources_with_permission(:teams)
|
298
|
+
```
|
299
|
+
|
275
300
|
## Development
|
276
301
|
|
277
302
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -11,6 +11,15 @@ module Recurso
|
|
11
11
|
(resource&.policy_class || Recurso::NilClassPolicy).new(self, resource)
|
12
12
|
end
|
13
13
|
|
14
|
+
def resources_with_permission(relation_name, action: :view, all_columns: true, include_actions: [])
|
15
|
+
policy(Recurso::Global.instance).resources_with_permission(
|
16
|
+
relation_name,
|
17
|
+
action: action,
|
18
|
+
all_columns: all_columns,
|
19
|
+
include_actions: include_actions
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
14
23
|
def policy_class
|
15
24
|
Recurso::BasePolicy
|
16
25
|
end
|
data/lib/recurso/config.rb
CHANGED
@@ -12,7 +12,8 @@ module Recurso
|
|
12
12
|
:default_level,
|
13
13
|
:identity_foreign_key,
|
14
14
|
:permission_class_name,
|
15
|
-
:default_permission_class_name
|
15
|
+
:default_permission_class_name,
|
16
|
+
:global_relations
|
16
17
|
|
17
18
|
def initialize
|
18
19
|
@levels_for_action = {
|
@@ -34,6 +35,8 @@ module Recurso
|
|
34
35
|
@identity_foreign_key = DEFAULT_IDENTITY_FOREIGN_KEY
|
35
36
|
|
36
37
|
@permission_class_name = DEFAULT_PERMISSION_CLASS_NAME
|
38
|
+
|
39
|
+
@global_relations = []
|
37
40
|
end
|
38
41
|
|
39
42
|
def model_specific(value, model)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Recurso
|
2
|
+
class Global
|
3
|
+
# no-op methods to support being a Recurso::Resource
|
4
|
+
define_singleton_method :has_one, ->(*args) {}
|
5
|
+
define_singleton_method :has_many, ->(*args) {}
|
6
|
+
define_singleton_method :belongs_to, ->(*args) {}
|
7
|
+
|
8
|
+
include Singleton
|
9
|
+
include Recurso::Resource
|
10
|
+
|
11
|
+
def permission_policy
|
12
|
+
OpenStruct.new(policy_type: :open)
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(method)
|
16
|
+
super unless Recurso::Config.instance.global_relations.include?(method)
|
17
|
+
|
18
|
+
method.to_s.classify.constantize.all
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -7,23 +7,14 @@ module Recurso
|
|
7
7
|
Recurso::Queries::Single.new(identity, resource, action).permission?
|
8
8
|
end
|
9
9
|
|
10
|
-
def resources_with_permission(relation_name, all_columns: true, include_actions: [:modify, :administer])
|
11
|
-
include_actions.reduce(resource_query_for(relation_name, :view, all_columns: all_columns)) do |resources, action|
|
12
|
-
resources
|
13
|
-
.joins("LEFT OUTER JOIN(#{resource_query_for(relation_name, action).to_sql}) AS #{action} ON #{action}.id = #{relation_name}.id")
|
14
|
-
.select("#{action}.id IS NOT NULL AS can_#{action}")
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def resource_query_for(relation_name, action, all_columns: false)
|
10
|
+
def resources_with_permission(relation_name, action: :view, all_columns: true, include_actions: [:modify, :administer])
|
21
11
|
Recurso::Queries::Relation.new(
|
22
12
|
identity,
|
23
13
|
resource,
|
24
14
|
relation_name,
|
25
15
|
all_columns: all_columns,
|
26
|
-
action: action
|
16
|
+
action: action,
|
17
|
+
include_actions: include_actions,
|
27
18
|
).resources
|
28
19
|
end
|
29
20
|
end
|
@@ -2,32 +2,56 @@ module Recurso
|
|
2
2
|
module Queries
|
3
3
|
class Relation
|
4
4
|
|
5
|
-
def initialize(identity, resource, relation_name, all_columns: true, action: :view)
|
5
|
+
def initialize(identity, resource, relation_name, all_columns: true, action: :view, include_actions: [])
|
6
6
|
@identity = identity
|
7
7
|
@resource = resource
|
8
8
|
@relation = resource.send(relation_name)
|
9
9
|
@all_columns = all_columns
|
10
|
+
@include_actions = (include_actions + Array(action)).uniq
|
10
11
|
@action = action
|
11
12
|
end
|
12
13
|
|
13
14
|
def resources
|
14
|
-
@
|
15
|
-
.select("#{@relation.table_name}.#{@all_columns ?
|
16
|
-
.
|
17
|
-
.
|
15
|
+
@relation
|
16
|
+
.select("#{@relation.table_name}.#{@all_columns ? :"*" : :id}")
|
17
|
+
.select(*@include_actions.map { |action| "included.can_#{action}"})
|
18
|
+
.joins("
|
19
|
+
INNER JOIN (#{included_permissions}) included
|
20
|
+
ON #{@relation.table_name}.id = included.id
|
21
|
+
AND included.can_#{@action} = 1
|
22
|
+
")
|
18
23
|
end
|
19
24
|
|
20
25
|
private
|
21
26
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def included_permissions
|
28
|
+
"
|
29
|
+
SELECT cascaded.id, #{included_permission_select}
|
30
|
+
FROM (#{cascaded_permissions}) cascaded
|
31
|
+
GROUP BY cascaded.id
|
32
|
+
"
|
33
|
+
end
|
34
|
+
|
35
|
+
def cascaded_permissions
|
36
|
+
@relation.relevant_associations.map do |assoc|
|
37
|
+
@relation
|
38
|
+
.select(:id, :level)
|
39
|
+
.joins(through_join_for(assoc))
|
40
|
+
.joins("
|
41
|
+
INNER join #{permission_class.table_name}
|
42
|
+
ON #{permission_class.table_name}.resource_type = '#{assoc.class_name}'
|
43
|
+
AND #{permission_class.table_name}.resource_id = #{resource_id_for(assoc)}
|
44
|
+
AND #{permission_class.table_name}.#{identity_foreign_key} = #{@identity.id.to_i}
|
45
|
+
").to_sql
|
46
|
+
end.join(" UNION ")
|
47
|
+
end
|
48
|
+
|
49
|
+
def included_permission_select
|
50
|
+
@include_actions.map do |action|
|
51
|
+
level_values = @resource.relevant_levels_for(action).map { |level| permission_class.levels[level] }.join(',')
|
52
|
+
|
53
|
+
"cascaded.level IN (#{level_values}) as can_#{action}"
|
54
|
+
end.join(", ")
|
31
55
|
end
|
32
56
|
|
33
57
|
def resource_id_for(assoc)
|
@@ -42,18 +66,6 @@ module Recurso
|
|
42
66
|
"
|
43
67
|
end
|
44
68
|
|
45
|
-
def coalesce(action)
|
46
|
-
"coalesce(#{level_columns}) IN (#{level_values(action)})"
|
47
|
-
end
|
48
|
-
|
49
|
-
def level_columns
|
50
|
-
@relation.relevant_associations.map { |assoc| "#{assoc.name}_permissions.level" }.join(',')
|
51
|
-
end
|
52
|
-
|
53
|
-
def level_values(action)
|
54
|
-
@resource.relevant_levels_for(action).map { |level| permission_class.levels[level] }.join(',')
|
55
|
-
end
|
56
|
-
|
57
69
|
def permission_class
|
58
70
|
@permission_class ||= Recurso::Config.instance.permission_class_name_for(@identity.class).constantize
|
59
71
|
end
|
data/lib/recurso/version.rb
CHANGED
data/lib/recurso.rb
CHANGED
@@ -4,6 +4,7 @@ require 'recurso/concerns/identity'
|
|
4
4
|
require 'recurso/concerns/resource'
|
5
5
|
require 'recurso/concerns/permission'
|
6
6
|
require 'recurso/concerns/controller'
|
7
|
+
require 'recurso/models/global'
|
7
8
|
require 'recurso/models/permission'
|
8
9
|
require 'recurso/models/permission_policy'
|
9
10
|
require 'recurso/policies/base_policy'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recurso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Kiesel
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07
|
11
|
+
date: 2021-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -152,6 +152,7 @@ files:
|
|
152
152
|
- lib/recurso/concerns/permission.rb
|
153
153
|
- lib/recurso/concerns/resource.rb
|
154
154
|
- lib/recurso/config.rb
|
155
|
+
- lib/recurso/models/global.rb
|
155
156
|
- lib/recurso/models/permission.rb
|
156
157
|
- lib/recurso/models/permission_policy.rb
|
157
158
|
- lib/recurso/policies/base_policy.rb
|