rolypoly 0.2.0 → 1.0.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/README.md +65 -7
- data/lib/rolypoly.rb +10 -0
- data/lib/rolypoly/controller_role_dsl.rb +28 -79
- data/lib/rolypoly/role_dsl.rb +57 -0
- data/lib/rolypoly/role_gatekeeper.rb +60 -42
- data/lib/rolypoly/role_gatekeepers.rb +54 -0
- data/lib/rolypoly/version.rb +1 -1
- data/spec/lib/rolypoly/controller_role_dsl_spec.rb +7 -11
- data/spec/lib/rolypoly/role_dsl_spec.rb +98 -0
- data/spec/lib/rolypoly/role_gatekeeper_spec.rb +33 -21
- data/spec/lib/rolypoly/role_gatekeepers_spec.rb +153 -0
- data/spec/spec_helper.rb +7 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da6a1122ac0be69431906e1943c0c0e6cbf7eb6a
|
4
|
+
data.tar.gz: e998ef6b5a0c1ed2655c1e09ebc31472bbdad714
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a7ef1c1c348c1176e74c70f42e8660f516e4ef3bcfd2f68327b699261ba385701980e687da7a6bc395ec58e38c47614dfda8d3034c47e85b22aee5131a67953
|
7
|
+
data.tar.gz: 1f77bce2c1d4464c2eaa4f47ecda8e6d75c3b1687872d4048bc0cd2efad4cf595211141305a2aec41e52852d244bd515b7705e085ecdb941c010b8d5070e89ed
|
data/README.md
CHANGED
@@ -19,7 +19,62 @@ And then execute:
|
|
19
19
|
$> bundle
|
20
20
|
```
|
21
21
|
|
22
|
-
## Usage
|
22
|
+
## Custom Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
role_checker = Rolypoly.define_gatekeepers do
|
26
|
+
allow(:super_duper_admin).to_all
|
27
|
+
allow(:super_admin).on(:organization).to_all
|
28
|
+
allow(:admin).on(:team).to_access(:show, :update)
|
29
|
+
end
|
30
|
+
|
31
|
+
role_checker_options = {
|
32
|
+
organization: ['Organization', team.organization_id],
|
33
|
+
team: team
|
34
|
+
}
|
35
|
+
|
36
|
+
role_checker.allow?(role_objects, :destroy, role_checker_options)
|
37
|
+
role_checker.allow?(role_objects, :destroy, role_checker_options)
|
38
|
+
```
|
39
|
+
|
40
|
+
## Policy Usage
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
class TeamPolicy < Struct.new(:user, :team)
|
44
|
+
|
45
|
+
include Rolypoly::RoleDSL
|
46
|
+
|
47
|
+
allow(:super_duper_admin).to_all
|
48
|
+
allow(:super_admin).on(:organization).to_all
|
49
|
+
allow(:admin).on(:team).to_access(:show, :update)
|
50
|
+
|
51
|
+
def show?
|
52
|
+
allow?(:show)
|
53
|
+
end
|
54
|
+
|
55
|
+
def update?
|
56
|
+
allow?(:update)
|
57
|
+
end
|
58
|
+
|
59
|
+
def destroy?
|
60
|
+
allow?(:destroy)
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_user_roles
|
64
|
+
current_user.role_assignments
|
65
|
+
end
|
66
|
+
|
67
|
+
def rolypoly_resource_map
|
68
|
+
{
|
69
|
+
organization: ['Organization', team.organization_id]
|
70
|
+
team: team
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
## Controller Usage
|
23
78
|
|
24
79
|
```ruby
|
25
80
|
class ApplicationController < ActionController::Base
|
@@ -98,8 +153,8 @@ end
|
|
98
153
|
|
99
154
|
This requires a method to be defined on `SomeCustomRoleObject` that checks if the resource is valid for that role.
|
100
155
|
|
101
|
-
The `
|
102
|
-
If `
|
156
|
+
The `rolypoly_resource_map` needs to be defined on the controller to pass the resources that the role will be validated against.
|
157
|
+
If `rolypoly_resource_map` is not defined it will be defaulted to an empty hash `{}`.
|
103
158
|
|
104
159
|
|
105
160
|
```ruby
|
@@ -110,8 +165,8 @@ class SomeCustomRoleObject
|
|
110
165
|
end
|
111
166
|
|
112
167
|
class ProfilesController < ApplicationController
|
113
|
-
|
114
|
-
|
168
|
+
allow(:admin).on(:organization).to_access(:index)
|
169
|
+
allow(:owner).on(:profile).to_access(:edit)
|
115
170
|
publicize(:show)
|
116
171
|
|
117
172
|
def index
|
@@ -130,8 +185,11 @@ class ProfilesController < ApplicationController
|
|
130
185
|
current_user.roles # => [#<SomeCustomRoleObject to_role_string: "admin", resource?: true>, #<SomeCustomRoleObject to_role_string: "scorekeeper", resource?: false>]
|
131
186
|
end
|
132
187
|
|
133
|
-
private def
|
134
|
-
{
|
188
|
+
private def rolypoly_resource_map
|
189
|
+
{
|
190
|
+
organization: ['Organization', tournament.org_id]
|
191
|
+
tournament: tournament
|
192
|
+
}
|
135
193
|
end
|
136
194
|
end
|
137
195
|
```
|
data/lib/rolypoly.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
require 'rolypoly/
|
1
|
+
require 'rolypoly/role_dsl'
|
2
|
+
|
2
3
|
module Rolypoly
|
3
4
|
FailedRoleCheckError = Class.new StandardError
|
4
5
|
module ControllerRoleDSL
|
6
|
+
|
5
7
|
def self.included(sub)
|
6
8
|
sub.before_filter(:rolypoly_check_role_access!) if sub.respond_to? :before_filter
|
7
9
|
if sub.respond_to? :rescue_from
|
@@ -14,101 +16,48 @@ module Rolypoly
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
unless sub.method_defined? :role_resource
|
22
|
-
define_method(:role_resource) { {} }
|
23
|
-
end
|
24
|
-
sub.send :extend, ClassMethods
|
25
|
-
end
|
26
|
-
|
27
|
-
def rolypoly_check_role_access!
|
28
|
-
failed_role_check! unless rolypoly_role_access?
|
29
|
-
end
|
30
|
-
|
31
|
-
def failed_role_check!
|
32
|
-
raise Rolypoly::FailedRoleCheckError
|
33
|
-
end
|
34
|
-
|
35
|
-
def current_roles
|
36
|
-
return [] if rolypoly_gatekeepers.empty?
|
37
|
-
current_gatekeepers.reduce([]) { |array, gatekeeper|
|
38
|
-
if gatekeeper.role?(current_user_roles, role_resource)
|
39
|
-
array += Array(gatekeeper.allowed_roles(current_user_roles, action_name, role_resource))
|
40
|
-
end
|
41
|
-
array
|
42
|
-
}
|
43
|
-
end
|
44
|
-
|
45
|
-
def public?
|
46
|
-
return true if rolypoly_gatekeepers.empty?
|
47
|
-
current_gatekeepers.any? &:public?
|
48
|
-
end
|
49
|
-
|
50
|
-
def current_gatekeepers
|
51
|
-
rolypoly_gatekeepers.select { |gatekeeper|
|
52
|
-
gatekeeper.action? action_name
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
|
-
def rolypoly_role_access?
|
57
|
-
rolypoly_gatekeepers.empty? ||
|
58
|
-
rolypoly_gatekeepers.any? { |gatekeeper|
|
59
|
-
gatekeeper.allow?(current_roles, action_name, role_resource)
|
60
|
-
}
|
61
|
-
end
|
62
|
-
private :rolypoly_role_access?
|
63
|
-
|
64
|
-
def rolypoly_gatekeepers
|
65
|
-
self.class.rolypoly_gatekeepers
|
19
|
+
sub.send(:include, RoleDSL)
|
20
|
+
sub.extend(ClassMethods)
|
21
|
+
sub.send(:include, InstanceMethods)
|
66
22
|
end
|
67
|
-
private :rolypoly_gatekeepers
|
68
23
|
|
69
|
-
module
|
70
|
-
def
|
71
|
-
|
24
|
+
module InstanceMethods
|
25
|
+
def rolypoly_check_role_access!
|
26
|
+
failed_role_check! unless rolypoly_role_access?
|
72
27
|
end
|
73
28
|
|
74
|
-
def
|
75
|
-
|
29
|
+
def failed_role_check!
|
30
|
+
raise Rolypoly::FailedRoleCheckError
|
76
31
|
end
|
77
32
|
|
78
|
-
def
|
79
|
-
|
33
|
+
def current_roles
|
34
|
+
allowed_roles(action_name)
|
80
35
|
end
|
81
36
|
|
82
|
-
def
|
83
|
-
|
37
|
+
def public?
|
38
|
+
super(action_name)
|
84
39
|
end
|
85
40
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
def rolypoly_gatekeepers
|
91
|
-
@rolypoly_gatekeepers ||= Array(try_super(__method__))
|
41
|
+
def current_gatekeepers
|
42
|
+
rolypoly_gatekeepers.select { |gatekeeper|
|
43
|
+
gatekeeper.action? action_name
|
44
|
+
}
|
92
45
|
end
|
93
46
|
|
94
|
-
def
|
95
|
-
|
96
|
-
super_val = superclass.send(mname)
|
97
|
-
super_val.respond_to?(:dup) ? super_val.dup : super_val
|
98
|
-
end
|
47
|
+
def allow?(options = {})
|
48
|
+
rolypoly_gatekeepers.allow?(current_roles, action_name, rolypoly_resource_map.merge(options))
|
99
49
|
end
|
100
50
|
|
101
|
-
def
|
102
|
-
|
103
|
-
rolypoly_gatekeepers << gatekeeper
|
104
|
-
}
|
51
|
+
private def rolypoly_role_access?
|
52
|
+
allow?
|
105
53
|
end
|
106
|
-
|
54
|
+
end
|
107
55
|
|
108
|
-
|
109
|
-
|
56
|
+
module ClassMethods
|
57
|
+
private def rolypoly_gatekeepers=(arry)
|
58
|
+
@rolypoly_gatekeepers = Rolypoly::RoleGatekeepers.new(arry)
|
110
59
|
end
|
111
|
-
private :rolypoly_gatekeepers=
|
112
60
|
end
|
61
|
+
|
113
62
|
end
|
114
63
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'rolypoly/role_gatekeepers'
|
3
|
+
|
4
|
+
module Rolypoly
|
5
|
+
module RoleDSL
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
base.send(:include, InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
unless base.method_defined?(:current_user_roles)
|
17
|
+
define_method(:current_user_roles) do
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
unless base.method_defined?(:rolypoly_resource_map)
|
23
|
+
define_method(:rolypoly_resource_map) do
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def_delegators 'self.class', :rolypoly_gatekeepers
|
30
|
+
def_delegators :rolypoly_gatekeepers, :public?
|
31
|
+
|
32
|
+
def allow?(action, options = {})
|
33
|
+
rolypoly_gatekeepers.allow?(current_user_roles, action, rolypoly_resource_map.merge(options))
|
34
|
+
end
|
35
|
+
|
36
|
+
def allowed_roles(action, options = {})
|
37
|
+
rolypoly_gatekeepers.allowed_roles(current_user_roles, action, rolypoly_resource_map.merge(options))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module ClassMethods
|
42
|
+
extend Forwardable
|
43
|
+
|
44
|
+
def inherited(subclass)
|
45
|
+
super
|
46
|
+
subclass.instance_variable_set('@rolypoly_gatekeepers', rolypoly_gatekeepers.dup)
|
47
|
+
end
|
48
|
+
|
49
|
+
def_delegators :rolypoly_gatekeepers, :all_public, :restrict, :allow, :allow?, :allowed_roles, :on, :public?, :publicize
|
50
|
+
|
51
|
+
def rolypoly_gatekeepers
|
52
|
+
@rolypoly_gatekeepers ||= Rolypoly::RoleGatekeepers.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -2,14 +2,37 @@ require 'set'
|
|
2
2
|
module Rolypoly
|
3
3
|
class RoleGatekeeper
|
4
4
|
attr_reader :roles
|
5
|
-
def initialize(roles, actions,
|
5
|
+
def initialize(roles, actions, resource = nil)
|
6
6
|
self.roles = Set.new Array(roles).map(&:to_s)
|
7
7
|
self.actions = Set.new Array(actions).map(&:to_s)
|
8
|
-
self.
|
8
|
+
self.resource = resource
|
9
9
|
self.all_actions = false
|
10
10
|
self.public = false
|
11
11
|
end
|
12
12
|
|
13
|
+
def initialize_copy(other)
|
14
|
+
@roles = @roles.dup
|
15
|
+
@actions = @actions.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
# on(resource).allow(*roles).to_access(*actions)
|
19
|
+
def allow(*roles)
|
20
|
+
to(*roles)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# on(resource).restrict(*actions).to(*roles)
|
25
|
+
def restrict(*actions)
|
26
|
+
to_access(*actions)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# allow(*roles).on(resource).to_access(*actions)
|
31
|
+
def on(resource)
|
32
|
+
self.resource = resource
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
13
36
|
# restrict(*actions).to *roles
|
14
37
|
def to(*roles)
|
15
38
|
self.roles = self.roles.merge roles.flatten.compact.map(&:to_s)
|
@@ -32,14 +55,13 @@ module Rolypoly
|
|
32
55
|
self.all_actions = true
|
33
56
|
end
|
34
57
|
|
35
|
-
def allow?(current_roles, action,
|
36
|
-
action?(action) &&
|
37
|
-
role?(current_roles, resource)
|
58
|
+
def allow?(current_roles, action, options = {})
|
59
|
+
action?(action) && role?(current_roles, options)
|
38
60
|
end
|
39
61
|
|
40
|
-
def allowed_roles(current_roles, action,
|
62
|
+
def allowed_roles(current_roles, action, options = {})
|
41
63
|
return [] if public? || !action?(action)
|
42
|
-
match_roles(current_roles,
|
64
|
+
match_roles(current_roles, options)
|
43
65
|
end
|
44
66
|
|
45
67
|
def all_public
|
@@ -47,10 +69,29 @@ module Rolypoly
|
|
47
69
|
self.all_actions = true
|
48
70
|
end
|
49
71
|
|
50
|
-
def role?(check_roles,
|
51
|
-
|
52
|
-
|
53
|
-
|
72
|
+
def role?(check_roles, options = {})
|
73
|
+
return true if public?
|
74
|
+
required_resource = find_required_resource(options)
|
75
|
+
|
76
|
+
Array(check_roles).any? do |check_role|
|
77
|
+
allowed_role?(check_role) && allowed_resource?(check_role, required_resource)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private def require_resource?
|
82
|
+
!!resource
|
83
|
+
end
|
84
|
+
|
85
|
+
private def allowed_resource?(check_role, required_resource)
|
86
|
+
return true unless require_resource?
|
87
|
+
|
88
|
+
check_role.respond_to?(:resource?) && check_role.resource?(required_resource)
|
89
|
+
end
|
90
|
+
|
91
|
+
private def find_required_resource(options = {})
|
92
|
+
return resource unless %w(String Symbol).include?(resource.class.to_s)
|
93
|
+
|
94
|
+
options[resource]
|
54
95
|
end
|
55
96
|
|
56
97
|
def action?(check_actions)
|
@@ -67,44 +108,21 @@ module Rolypoly
|
|
67
108
|
attr_accessor :actions
|
68
109
|
attr_accessor :all_actions
|
69
110
|
attr_accessor :public
|
70
|
-
attr_accessor :
|
111
|
+
attr_accessor :resource
|
71
112
|
|
72
|
-
def match_roles(check_roles,
|
73
|
-
|
74
|
-
check_roles.reduce([]) { |array, role_object|
|
75
|
-
array << role_object if roles.include?(sanitize_role_object(role_object))
|
76
|
-
array
|
77
|
-
}
|
78
|
-
end
|
79
|
-
private :match_roles
|
113
|
+
private def match_roles(check_roles, options = {})
|
114
|
+
required_resource = find_required_resource(options)
|
80
115
|
|
81
|
-
|
82
|
-
|
83
|
-
check_roles.select do |check_role|
|
84
|
-
check_role.respond_to?(:resource?) && check_role.resource?(resource)
|
116
|
+
Array(check_roles).select do |check_role|
|
117
|
+
allowed_role?(check_role) && allowed_resource?(check_role, required_resource)
|
85
118
|
end
|
86
119
|
end
|
87
|
-
private :filter_roles_by_resource
|
88
|
-
|
89
|
-
def sanitize_role_input(role_objects)
|
90
|
-
Array(role_objects).map { |r| sanitize_role_object(r) }
|
91
|
-
end
|
92
|
-
private :sanitize_role_input
|
93
120
|
|
94
|
-
def
|
95
|
-
role_object.respond_to?(:to_role_string) ? role_object.to_role_string : role_object.to_s
|
96
|
-
end
|
97
|
-
private :sanitize_role_object
|
98
|
-
|
99
|
-
def can_set_with_to?
|
100
|
-
roles.empty?
|
101
|
-
end
|
102
|
-
private :can_set_with_to?
|
121
|
+
private def allowed_role?(role_object)
|
122
|
+
role_string = role_object.respond_to?(:to_role_string) ? role_object.to_role_string : role_object.to_s
|
103
123
|
|
104
|
-
|
105
|
-
actions.empty?
|
124
|
+
roles.include?(role_string.to_s)
|
106
125
|
end
|
107
|
-
private :can_set_with_access_to?
|
108
126
|
|
109
127
|
def all_actions?
|
110
128
|
!!all_actions
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'rolypoly/role_gatekeeper'
|
3
|
+
|
4
|
+
module Rolypoly
|
5
|
+
class RoleGatekeepers
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def_delegators :build_gatekeeper, :all_public, :allow, :on, :restrict
|
11
|
+
def_delegators :@gatekeepers, :clear, :each, :empty?
|
12
|
+
|
13
|
+
def initialize(gatekeepers = [])
|
14
|
+
@gatekeepers = Array(gatekeepers)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_copy(other)
|
18
|
+
@gatekeepers = @gatekeepers.map(&:dup)
|
19
|
+
end
|
20
|
+
|
21
|
+
def publicize(*actions)
|
22
|
+
restrict(*actions).to_none
|
23
|
+
end
|
24
|
+
|
25
|
+
def allow?(role_objects, action, options = {})
|
26
|
+
return true if empty?
|
27
|
+
|
28
|
+
any? { |gatekeeper| gatekeeper.allow?(role_objects, action, options) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def allowed_roles(role_objects, action, options = {})
|
32
|
+
return [] if empty?
|
33
|
+
|
34
|
+
reduce([]) do |allowed_role_objects, gatekeeper|
|
35
|
+
allowed_role_objects | gatekeeper.allowed_roles(role_objects, action, options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def public?(action)
|
40
|
+
return true if empty?
|
41
|
+
|
42
|
+
any? do |gatekeeper|
|
43
|
+
gatekeeper.action?(action) && gatekeeper.public?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private def build_gatekeeper(roles = nil, actions = nil, resource = nil)
|
48
|
+
new_gatekeeper = RoleGatekeeper.new(roles, actions, resource)
|
49
|
+
@gatekeepers << new_gatekeeper
|
50
|
+
new_gatekeeper
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/rolypoly/version.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
def to_s
|
4
|
-
name.to_s
|
5
|
-
end
|
6
|
-
end
|
2
|
+
|
7
3
|
module Rolypoly
|
8
4
|
describe ControllerRoleDSL do
|
9
5
|
let(:example_controller) do
|
@@ -15,7 +11,7 @@ module Rolypoly
|
|
15
11
|
subject { example_controller }
|
16
12
|
it { should respond_to :restrict }
|
17
13
|
it { should respond_to :allow }
|
18
|
-
it { should respond_to :
|
14
|
+
it { should respond_to :on }
|
19
15
|
|
20
16
|
describe "setting up with DSL" do
|
21
17
|
describe "from allow side" do
|
@@ -78,21 +74,21 @@ module Rolypoly
|
|
78
74
|
end
|
79
75
|
end
|
80
76
|
|
81
|
-
describe "from
|
77
|
+
describe "from on side" do
|
82
78
|
let(:controller_instance) { subject.new }
|
83
79
|
let(:admin_role) { RoleObject.new(:admin) }
|
84
80
|
let(:scorekeeper_role) { RoleObject.new(:scorekeeper) }
|
85
81
|
let(:current_user_roles) { [admin_role, scorekeeper_role] }
|
86
|
-
let(:
|
82
|
+
let(:rolypoly_resource_map) { { organization: ['Organization', 123] } }
|
87
83
|
let(:check_access!) { controller_instance.rolypoly_check_role_access! }
|
88
84
|
|
89
85
|
before do
|
90
|
-
subject.
|
86
|
+
subject.on(:organization).allow(:admin).to_access(:index)
|
91
87
|
subject.publicize(:landing)
|
92
|
-
allow(admin_role).to receive(:resource?).and_return true
|
88
|
+
allow(admin_role).to receive(:resource?).with(rolypoly_resource_map[:organization]).and_return true
|
93
89
|
allow(controller_instance).to receive(:current_user_roles).and_return(current_user_roles)
|
94
90
|
allow(controller_instance).to receive(:action_name).and_return(action_name)
|
95
|
-
allow(controller_instance).to receive(:
|
91
|
+
allow(controller_instance).to receive(:rolypoly_resource_map).and_return(rolypoly_resource_map)
|
96
92
|
end
|
97
93
|
|
98
94
|
describe "#index" do
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rolypoly
|
4
|
+
describe RoleDSL do
|
5
|
+
subject do
|
6
|
+
Class.new do
|
7
|
+
include Rolypoly::RoleDSL
|
8
|
+
end
|
9
|
+
end
|
10
|
+
after { subject.instance_variable_set('@rolypoly_gatekeepers', nil) }
|
11
|
+
|
12
|
+
it { expect(subject).to respond_to :rolypoly_gatekeepers }
|
13
|
+
|
14
|
+
it { expect(subject).to respond_to :all_public }
|
15
|
+
it 'should delegate all_public to rolypoly_gatekeepers' do
|
16
|
+
expect(subject.rolypoly_gatekeepers).to receive(:all_public)
|
17
|
+
subject.all_public
|
18
|
+
end
|
19
|
+
|
20
|
+
it { expect(subject).to respond_to :allow }
|
21
|
+
it 'should delegate allow to rolypoly_gatekeepers' do
|
22
|
+
expect(subject.rolypoly_gatekeepers).to receive(:allow).with(:role_1, :role_2)
|
23
|
+
subject.allow(:role_1, :role_2)
|
24
|
+
end
|
25
|
+
|
26
|
+
it { expect(subject).to respond_to :allow? }
|
27
|
+
it 'should delegate allow? to rolypoly_gatekeepers' do
|
28
|
+
expect(subject.rolypoly_gatekeepers).to receive(:allow?).with([:admin, :org_admin], :index)
|
29
|
+
subject.allow?([:admin, :org_admin], :index)
|
30
|
+
end
|
31
|
+
|
32
|
+
it { expect(subject).to respond_to :allowed_roles }
|
33
|
+
it 'should delegate allowed_roles to rolypoly_gatekeepers' do
|
34
|
+
expect(subject.rolypoly_gatekeepers).to receive(:allowed_roles).with([:admin, :org_admin], :index)
|
35
|
+
subject.allowed_roles([:admin, :org_admin], :index)
|
36
|
+
end
|
37
|
+
|
38
|
+
it { expect(subject).to respond_to :on }
|
39
|
+
it 'should delegate on to rolypoly_gatekeepers' do
|
40
|
+
expect(subject.rolypoly_gatekeepers).to receive(:on).with(:organization)
|
41
|
+
subject.on(:organization)
|
42
|
+
end
|
43
|
+
|
44
|
+
it { expect(subject).to respond_to :publicize }
|
45
|
+
it 'should delegate publicize to rolypoly_gatekeepers' do
|
46
|
+
expect(subject.rolypoly_gatekeepers).to receive(:publicize).with(:index, :show)
|
47
|
+
subject.publicize(:index, :show)
|
48
|
+
end
|
49
|
+
|
50
|
+
it { expect(subject).to respond_to :public? }
|
51
|
+
it 'should delegate public? to rolypoly_gatekeepers' do
|
52
|
+
expect(subject.rolypoly_gatekeepers).to receive(:public?).with(:index)
|
53
|
+
subject.public?(:index)
|
54
|
+
end
|
55
|
+
|
56
|
+
it { expect(subject).to respond_to :restrict }
|
57
|
+
it 'should delegate restrict to rolypoly_gatekeepers' do
|
58
|
+
expect(subject.rolypoly_gatekeepers).to receive(:restrict).with(:index, :show)
|
59
|
+
subject.restrict(:index, :show)
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'instance' do
|
63
|
+
let(:example_object) { subject.new }
|
64
|
+
|
65
|
+
it { expect(example_object).to respond_to :current_user_roles }
|
66
|
+
it { expect(example_object).to respond_to :rolypoly_resource_map }
|
67
|
+
|
68
|
+
it { expect(example_object).to respond_to :rolypoly_gatekeepers }
|
69
|
+
it 'should delegate rolypoly_gatekeepers to self.class' do
|
70
|
+
expect(subject).to receive(:rolypoly_gatekeepers)
|
71
|
+
example_object.rolypoly_gatekeepers
|
72
|
+
end
|
73
|
+
|
74
|
+
it { expect(subject).to respond_to :public? }
|
75
|
+
it 'should delegate public? to rolypoly_gatekeepers' do
|
76
|
+
expect(subject.rolypoly_gatekeepers).to receive(:public?).with(:index)
|
77
|
+
subject.public?(:index)
|
78
|
+
end
|
79
|
+
|
80
|
+
%w(allow? allowed_roles).each do |method_name|
|
81
|
+
it { expect(example_object).to respond_to method_name }
|
82
|
+
|
83
|
+
it "should delegate #{method_name} to rolypoly_gatekeepers" do
|
84
|
+
current_user_roles = [:admin, :org_admin]
|
85
|
+
rolypoly_resource_map = { foo: :foo, bar: :bar }
|
86
|
+
options = { bar: :baz }
|
87
|
+
|
88
|
+
expect(example_object).to receive(:current_user_roles).and_return(current_user_roles)
|
89
|
+
expect(example_object).to receive(:rolypoly_resource_map).and_return(rolypoly_resource_map)
|
90
|
+
expect(example_object.rolypoly_gatekeepers).to receive(method_name).with(current_user_roles, :index, { foo: :foo, bar: :baz })
|
91
|
+
|
92
|
+
example_object.public_send(method_name, :index, options)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -4,22 +4,21 @@ module Rolypoly
|
|
4
4
|
describe RoleGatekeeper do
|
5
5
|
let(:roles) { %w[admin scorekeeper] }
|
6
6
|
let(:actions) { %w[index show] }
|
7
|
-
let(:resource) { {} }
|
8
7
|
|
9
8
|
context "resource not required" do
|
10
|
-
subject { described_class.new roles, actions
|
9
|
+
subject { described_class.new roles, actions }
|
11
10
|
|
12
11
|
shared_examples_for "allow should behave correctly" do
|
13
12
|
it "shouldn't auto-allow" do
|
14
|
-
expect(subject.allow?(nil, nil
|
13
|
+
expect(subject.allow?(nil, nil)).to be false
|
15
14
|
end
|
16
15
|
|
17
16
|
it "should allow scorekeepr access to index" do
|
18
|
-
expect(subject.allow?([:scorekeeper], "index"
|
17
|
+
expect(subject.allow?([:scorekeeper], "index")).to be true
|
19
18
|
end
|
20
19
|
|
21
20
|
it "should not allow scorekeepr access to edit" do
|
22
|
-
expect(subject.allow?([:scorekeeper], "edit"
|
21
|
+
expect(subject.allow?([:scorekeeper], "edit")).to be false
|
23
22
|
end
|
24
23
|
|
25
24
|
describe "all public" do
|
@@ -28,15 +27,15 @@ module Rolypoly
|
|
28
27
|
end
|
29
28
|
|
30
29
|
it "should allow whatever" do
|
31
|
-
expect(subject.allow?(nil, nil
|
30
|
+
expect(subject.allow?(nil, nil)).to be true
|
32
31
|
end
|
33
32
|
|
34
33
|
it "should allow scorekeepr access to index" do
|
35
|
-
expect(subject.allow?([:scorekeeper], "index"
|
34
|
+
expect(subject.allow?([:scorekeeper], "index")).to be true
|
36
35
|
end
|
37
36
|
|
38
37
|
it "should allow scorekeepr access to edit" do
|
39
|
-
expect(subject.allow?([:scorekeeper], "edit"
|
38
|
+
expect(subject.allow?([:scorekeeper], "edit")).to be true
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
@@ -46,17 +45,17 @@ module Rolypoly
|
|
46
45
|
end
|
47
46
|
|
48
47
|
it "shouldn't auto-allow" do
|
49
|
-
expect(subject.allow?(nil, nil
|
48
|
+
expect(subject.allow?(nil, nil)).to be false
|
50
49
|
end
|
51
50
|
|
52
51
|
it "should allow scorekeepr access to index" do
|
53
|
-
expect(subject.allow?([:janitor], "index"
|
54
|
-
expect(subject.allow?([:admin], "index"
|
52
|
+
expect(subject.allow?([:janitor], "index")).to be true
|
53
|
+
expect(subject.allow?([:admin], "index")).to be true
|
55
54
|
end
|
56
55
|
|
57
56
|
it "to should not allow scorekeepr access to edit" do
|
58
|
-
expect(subject.allow?([:scorekeeper], "edit"
|
59
|
-
expect(subject.allow?([:janitor], "edit"
|
57
|
+
expect(subject.allow?([:scorekeeper], "edit")).to be false
|
58
|
+
expect(subject.allow?([:janitor], "edit")).to be false
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
@@ -66,19 +65,19 @@ module Rolypoly
|
|
66
65
|
end
|
67
66
|
|
68
67
|
it "shouldn't auto-allow" do
|
69
|
-
expect(subject.allow?(nil, nil
|
68
|
+
expect(subject.allow?(nil, nil)).to be false
|
70
69
|
end
|
71
70
|
|
72
71
|
it "should allow scorekeepr access to index" do
|
73
|
-
expect(subject.allow?([:scorekeeper], "index"
|
72
|
+
expect(subject.allow?([:scorekeeper], "index")).to be true
|
74
73
|
end
|
75
74
|
|
76
75
|
it "shouldn't allow janitor access to any" do
|
77
|
-
expect(subject.allow?([:janitor], "index"
|
76
|
+
expect(subject.allow?([:janitor], "index")).to be false
|
78
77
|
end
|
79
78
|
|
80
79
|
it "should allow scorekeepr access to edit" do
|
81
|
-
expect(subject.allow?([:scorekeeper], "edit"
|
80
|
+
expect(subject.allow?([:scorekeeper], "edit")).to be true
|
82
81
|
end
|
83
82
|
end
|
84
83
|
end
|
@@ -118,11 +117,24 @@ module Rolypoly
|
|
118
117
|
context "resource required" do
|
119
118
|
let(:scorekeeper_role) { RoleObject.new(:scorekeeper) }
|
120
119
|
|
121
|
-
subject { described_class.new roles, actions,
|
120
|
+
subject { described_class.new roles, actions, :resource }
|
122
121
|
|
123
122
|
describe "resource does not match" do
|
124
123
|
before do
|
125
|
-
allow(scorekeeper_role).to receive(:resource?).and_return false
|
124
|
+
allow(scorekeeper_role).to receive(:resource?).with(nil).and_return false
|
125
|
+
allow(scorekeeper_role).to receive(:to_role_string).and_return 'scorekeeper'
|
126
|
+
end
|
127
|
+
|
128
|
+
it { expect(subject.allow?(nil, nil)).to be false }
|
129
|
+
it { expect(subject.allow?(scorekeeper_role, :index)).to be false }
|
130
|
+
it { expect(subject.allow?(scorekeeper_role, :edit)).to be false }
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "resource does not match" do
|
134
|
+
let(:resource) { { resource: ['Organization', 123] } }
|
135
|
+
|
136
|
+
before do
|
137
|
+
allow(scorekeeper_role).to receive(:resource?).with(resource[:resource]).and_return false
|
126
138
|
allow(scorekeeper_role).to receive(:to_role_string).and_return "scorekeeper"
|
127
139
|
end
|
128
140
|
|
@@ -132,10 +144,10 @@ module Rolypoly
|
|
132
144
|
end
|
133
145
|
|
134
146
|
describe "resource matches" do
|
135
|
-
let(:resource) { {resource: 123} }
|
147
|
+
let(:resource) { { resource: ['Organization', 123] } }
|
136
148
|
|
137
149
|
before do
|
138
|
-
allow(scorekeeper_role).to receive(:resource?).and_return true
|
150
|
+
allow(scorekeeper_role).to receive(:resource?).with(resource[:resource]).and_return true
|
139
151
|
end
|
140
152
|
|
141
153
|
it { expect(subject.allow?(nil, nil, resource)).to be false }
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Rolypoly
|
4
|
+
describe RoleGatekeepers do
|
5
|
+
subject { RoleGatekeepers.new }
|
6
|
+
after { subject.clear }
|
7
|
+
|
8
|
+
# DSL class methods
|
9
|
+
describe 'rule-building methods' do
|
10
|
+
it { should respond_to :all_public }
|
11
|
+
it { expect { subject.all_public }.to change { subject.to_a.size }.by(1) }
|
12
|
+
it 'should delegate all_public to a new gatekeeper' do
|
13
|
+
expect_any_instance_of(RoleGatekeeper).to receive(:all_public)
|
14
|
+
subject.all_public
|
15
|
+
end
|
16
|
+
|
17
|
+
it { should respond_to :allow }
|
18
|
+
it { expect { subject.allow(:role_1, :role_2) }.to change { subject.to_a.size }.by(1) }
|
19
|
+
it 'should delegate allow to a new gatekeeper' do
|
20
|
+
expect_any_instance_of(RoleGatekeeper).to receive(:allow).with(:role_1, :role_2)
|
21
|
+
subject.allow(:role_1, :role_2)
|
22
|
+
end
|
23
|
+
|
24
|
+
it { should respond_to :on }
|
25
|
+
it { expect { subject.on(:organization) }.to change { subject.to_a.size }.by(1) }
|
26
|
+
it 'should delegate on to a new gatekeeper' do
|
27
|
+
expect_any_instance_of(RoleGatekeeper).to receive(:on).with(:organization)
|
28
|
+
subject.on(:organization)
|
29
|
+
end
|
30
|
+
|
31
|
+
it { should respond_to :publicize }
|
32
|
+
it { expect { subject.publicize }.to change { subject.to_a.size }.by(1) }
|
33
|
+
it 'should delegate publicize to a new gatekeeper' do
|
34
|
+
newer_gatekeeper = instance_double('RoleGatekeeper', :newer_gatekeeper)
|
35
|
+
expect_any_instance_of(RoleGatekeeper).to receive(:restrict).with(:index, :show).and_return(newer_gatekeeper)
|
36
|
+
expect(newer_gatekeeper).to receive(:to_none)
|
37
|
+
subject.publicize(:index, :show)
|
38
|
+
end
|
39
|
+
|
40
|
+
it { should respond_to :restrict }
|
41
|
+
it { expect { subject.restrict(:index, :show) }.to change { subject.to_a.size }.by(1) }
|
42
|
+
it 'should delegate restrict to a new gatekeeper' do
|
43
|
+
expect_any_instance_of(RoleGatekeeper).to receive(:restrict).with(:index, :show)
|
44
|
+
subject.restrict(:index, :show)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# DSL instance methods
|
49
|
+
it { should respond_to :allow? }
|
50
|
+
it { should respond_to :allowed_roles }
|
51
|
+
it { should respond_to :public? }
|
52
|
+
|
53
|
+
# Array methods not included in Enumerable
|
54
|
+
it { should respond_to :clear }
|
55
|
+
it { should respond_to :empty? }
|
56
|
+
|
57
|
+
describe 'setting up with DSL' do
|
58
|
+
describe 'from allow side' do
|
59
|
+
let(:current_user_roles) { [RoleObject.new(:admin), RoleObject.new(:scorekeeper)] }
|
60
|
+
before do
|
61
|
+
subject.allow(:admin).to_access(:index)
|
62
|
+
subject.publicize(:landing)
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#index' do
|
66
|
+
let(:action_name) { 'index' }
|
67
|
+
|
68
|
+
it 'is not public' do
|
69
|
+
expect(subject).to_not be_public(action_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'allows admin access' do
|
73
|
+
expect(subject).to be_allow(current_user_roles, action_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can get current_user_roles' do
|
77
|
+
expect(subject.allowed_roles(current_user_roles, action_name)).to eq([RoleObject.new(:admin)])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#show' do
|
82
|
+
let(:action_name) { 'show' }
|
83
|
+
it 'disallows admin access' do
|
84
|
+
expect(subject).to_not be_allow(current_user_roles, action_name)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'is not public' do
|
88
|
+
expect(subject).to_not be_public(action_name)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#landing' do
|
93
|
+
let(:action_name) { 'landing' }
|
94
|
+
it 'allows admin access' do
|
95
|
+
expect(subject).to be_allow(current_user_roles, action_name)
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'with no role' do
|
99
|
+
let(:current_user_roles) { [] }
|
100
|
+
it 'allows admin access' do
|
101
|
+
expect(subject).to be_allow(current_user_roles, action_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'is public' do
|
105
|
+
expect(subject).to be_public(action_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'from on side' do
|
112
|
+
let(:admin_role) { RoleObject.new(:admin) }
|
113
|
+
let(:scorekeeper_role) { RoleObject.new(:scorekeeper) }
|
114
|
+
let(:current_user_roles) { [admin_role, scorekeeper_role] }
|
115
|
+
let(:rolypoly_resource_map) { { organization: ['Organization', 123] } }
|
116
|
+
|
117
|
+
before do
|
118
|
+
subject.on(:organization).allow(:admin).to_access(:index)
|
119
|
+
subject.publicize(:landing)
|
120
|
+
allow(admin_role).to receive(:resource?).with(rolypoly_resource_map[:organization]).and_return true
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#index' do
|
124
|
+
let(:action_name) { 'index' }
|
125
|
+
|
126
|
+
it { expect(subject).to_not be_public(action_name) }
|
127
|
+
it { expect(subject).to be_allow(current_user_roles, action_name, rolypoly_resource_map) }
|
128
|
+
it { expect(subject.allowed_roles(current_user_roles, action_name, rolypoly_resource_map)).to eq([RoleObject.new(:admin)])}
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#show' do
|
132
|
+
let(:action_name) { 'show' }
|
133
|
+
|
134
|
+
it { expect(subject).to_not be_allow(current_user_roles, action_name) }
|
135
|
+
it { expect(subject).to_not be_public(action_name) }
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#landing' do
|
139
|
+
let(:action_name) { 'landing' }
|
140
|
+
|
141
|
+
it { expect(subject).to be_allow(current_user_roles, action_name) }
|
142
|
+
|
143
|
+
describe 'with no role' do
|
144
|
+
let(:current_user_roles) { [] }
|
145
|
+
|
146
|
+
it { expect(subject).to be_allow(current_user_roles, action_name) }
|
147
|
+
it { expect(subject).to be_public(action_name) }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -5,6 +5,13 @@
|
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
7
|
require 'rolypoly'
|
8
|
+
|
9
|
+
class RoleObject < Struct.new(:name)
|
10
|
+
def to_s
|
11
|
+
name.to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
RSpec.configure do |config|
|
9
16
|
config.run_all_when_everything_filtered = true
|
10
17
|
config.filter_run :focus
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rolypoly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Phenow
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -71,11 +71,15 @@ files:
|
|
71
71
|
- Rakefile
|
72
72
|
- lib/rolypoly.rb
|
73
73
|
- lib/rolypoly/controller_role_dsl.rb
|
74
|
+
- lib/rolypoly/role_dsl.rb
|
74
75
|
- lib/rolypoly/role_gatekeeper.rb
|
76
|
+
- lib/rolypoly/role_gatekeepers.rb
|
75
77
|
- lib/rolypoly/version.rb
|
76
78
|
- rolypoly.gemspec
|
77
79
|
- spec/lib/rolypoly/controller_role_dsl_spec.rb
|
80
|
+
- spec/lib/rolypoly/role_dsl_spec.rb
|
78
81
|
- spec/lib/rolypoly/role_gatekeeper_spec.rb
|
82
|
+
- spec/lib/rolypoly/role_gatekeepers_spec.rb
|
79
83
|
- spec/spec_helper.rb
|
80
84
|
homepage: https://github.com/sportngin/rolypoly
|
81
85
|
licenses:
|
@@ -97,11 +101,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
101
|
version: '0'
|
98
102
|
requirements: []
|
99
103
|
rubyforge_project:
|
100
|
-
rubygems_version: 2.
|
104
|
+
rubygems_version: 2.6.10
|
101
105
|
signing_key:
|
102
106
|
specification_version: 4
|
103
107
|
summary: Tools for handling per-action and per-app Role authorization
|
104
108
|
test_files:
|
105
109
|
- spec/lib/rolypoly/controller_role_dsl_spec.rb
|
110
|
+
- spec/lib/rolypoly/role_dsl_spec.rb
|
106
111
|
- spec/lib/rolypoly/role_gatekeeper_spec.rb
|
112
|
+
- spec/lib/rolypoly/role_gatekeepers_spec.rb
|
107
113
|
- spec/spec_helper.rb
|