rolypoly 1.0.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rolypoly might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1edb5688538c2b284ffedf79a379a70e92073484
4
- data.tar.gz: b1e672c8bb90ce9b2061a4ffb3c3aa93f9696865
2
+ SHA256:
3
+ metadata.gz: 8e4740fb3c4333e32b0fa17cd46c6069ac7f4e5a9fb8fd0d634f37a372b1cb58
4
+ data.tar.gz: f95e52673fc7211cc17ec683f5727bfd5b4d3b4b653c1db571e7eace3ce91a39
5
5
  SHA512:
6
- metadata.gz: 1fdab74446c185aff377f3cb4ef238cea5ff23237001d4401d11ceb34b102ffa16d2661272084a42e04b992011e074dcd296920a4ed7e17ff04e5927eeab6f5e
7
- data.tar.gz: 54e68d95397017a05f272ef9d708d808d4f8c2c456f8b81d6837e9f4ab2e81f649bf9f6a31661d811a5e3094393c73125a1accf5af8ac85f6de061396445fe71
6
+ metadata.gz: 6f34a19e2704d544039bcbe4c8ec95647a3a2498be2bac9d8d91fe9e97222b164f026dd171ba7f3fc7019d445297d11a465835c17efe4e63a9712be11d788d3b
7
+ data.tar.gz: ccd41a27601cb2b182cbd4363da214f6c63dd54864a148a6753f2cbd07257daf78d3e72d34b77a017793a6422d85804ee67011c832b7e033bcf6234301dc6de4
@@ -1 +1 @@
1
- 2.2.0
1
+ 2.6.5
data/README.md CHANGED
@@ -19,6 +19,8 @@ And then execute:
19
19
  $> bundle
20
20
  ```
21
21
 
22
+ NOTE: If your application is running Rails 4.X.X or lower, please ensure your application also includes the `where-or` gem.
23
+
22
24
  ## Custom Usage
23
25
 
24
26
  ```ruby
@@ -1,5 +1,6 @@
1
1
  require "rolypoly/version"
2
2
  require 'rolypoly/controller_role_dsl'
3
+ require 'rolypoly/resource_index_query_builder'
3
4
 
4
5
  module Rolypoly
5
6
 
@@ -5,11 +5,15 @@ module Rolypoly
5
5
  module ControllerRoleDSL
6
6
 
7
7
  def self.included(sub)
8
- sub.before_filter(:rolypoly_check_role_access!) if sub.respond_to? :before_filter
9
- if sub.respond_to? :rescue_from
8
+ if sub.respond_to? :before_filter
9
+ sub.before_filter(:rolypoly_check_role_access!)
10
+ elsif sub.respond_to? :before_action
11
+ sub.before_action(:rolypoly_check_role_access!)
12
+ end
13
+ if sub.respond_to? :rescue_from
10
14
  sub.rescue_from(FailedRoleCheckError) do
11
15
  respond_to do |f|
12
- f.html { render text: "Not Authorized", status: 401 }
16
+ f.html { render plain: "Not Authorized", status: 401 }
13
17
  f.json { render json: { error: "Not Authorized" }, status: 401 }
14
18
  f.xml { render xml: { error: "Not Authorized" }, status: 401 }
15
19
  end
@@ -0,0 +1,86 @@
1
+ require 'forwardable'
2
+ require 'rolypoly/role_scopes'
3
+
4
+ module Rolypoly
5
+ module IndexRoleDSL
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
+ base.send(:define_method, :current_user_roles) do
18
+ raise NotImplementedError
19
+ end
20
+ end
21
+ end
22
+
23
+ def_delegators 'self.class', :role_scopes
24
+ def_delegators :role_scopes, :public?
25
+
26
+ def apply_scopes
27
+ return query if role_scopes.all_access?(current_user_roles)
28
+ return query.none if scope_hash.empty?
29
+ if scope_hash.keys.length == 1
30
+ scope_hash.inject(query) { |query, (scope_name, ids)| query.public_send(scope_name, ids) }
31
+ else
32
+ # This block is designed to handle cases of more than one scope.
33
+ # The following code has been demonstrated to perform on most "simple"...
34
+ #...objects and scopes. However, there are certain complex objects...
35
+ #...and scopes that cause permission checks to fail.
36
+ object_query = query
37
+ object_query = join_tables.inject(object_query) do |q, join_table|
38
+ q.joins(join_table)
39
+ end
40
+
41
+ scope_hash.inject(object_query) do |object_query, (scope_name, ids)|
42
+ object_query.or(query.public_send(scope_name, ids))
43
+ end
44
+ end
45
+ end
46
+
47
+ def join_tables
48
+ scope_hash.map do |scope_name, ids|
49
+ query.public_send(scope_name, ids).values[:joins]
50
+ end
51
+ .flatten
52
+ .uniq
53
+ .reject { |join_table| query_join_tables.include?(join_table) }
54
+ end
55
+
56
+ def query_join_tables
57
+ values = query.try(:values) || {}
58
+ values.fetch(:joins, [])
59
+ end
60
+
61
+
62
+ def scope_hash
63
+ @scope_hash ||= role_scopes.scope_hash(current_user_roles)
64
+ end
65
+
66
+ def allowed_roles(scope_name)
67
+ role_scopes.allowed_roles(current_user_roles, scope_name)
68
+ end
69
+ end
70
+
71
+ module ClassMethods
72
+ extend Forwardable
73
+
74
+ def inherited(subclass)
75
+ super
76
+ subclass.instance_variable_set('@role_scopes', role_scopes.dup)
77
+ end
78
+
79
+ def_delegators :role_scopes, :all_public, :allow, :allowed_roles, :on
80
+
81
+ def role_scopes
82
+ @role_scopes ||= Rolypoly::RoleScopes.new
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,22 @@
1
+ require 'rolypoly/index_role_dsl'
2
+
3
+ module Rolypoly
4
+ class ResourceIndexQueryBuilder
5
+ include Rolypoly::IndexRoleDSL
6
+
7
+ attr_reader :query, :user
8
+
9
+ allow(:third_north).to_all
10
+ allow(:org_admin).on(:organization).to_access(:org_id)
11
+ allow(:tournament_director).on(:organization).to_access(:org_id)
12
+
13
+ def initialize(query, user)
14
+ @query = query
15
+ @user = user
16
+ end
17
+
18
+ private def current_user_roles
19
+ user.role_assignments
20
+ end
21
+ end
22
+ end
@@ -84,10 +84,18 @@ module Rolypoly
84
84
 
85
85
  private def allowed_resource?(check_role, required_resource)
86
86
  return true unless require_resource?
87
- return false unless check_role.respond_to?(:resource?)
88
87
 
89
- required_resources = type_id_resource?(required_resource) ? [required_resource] : Array(required_resource)
90
- required_resources.any? {|r| check_role.resource?(r) }
88
+ case required_resource
89
+ when ->(r) { r.is_a?(Hash) && r.keys == [:resource_type] }
90
+ check_role.respond_to?(:resource_type) &&
91
+ check_role.resource_type == required_resource[:resource_type]
92
+ when Hash, ->(r) { type_id_resource?(r) }
93
+ check_role.respond_to?(:resource?) &&
94
+ check_role.resource?(required_resource)
95
+ else
96
+ check_role.respond_to?(:resource?) &&
97
+ Array(required_resource).any? {|r| check_role.resource?(r) }
98
+ end
91
99
  end
92
100
 
93
101
  private def type_id_resource?(required_resource)
@@ -0,0 +1,92 @@
1
+ require 'set'
2
+
3
+ module Rolypoly
4
+ class RoleScope
5
+ attr_reader :roles, :actions
6
+ def initialize(roles, actions, resource = nil)
7
+ self.roles = Set.new Array(roles).map(&:to_s)
8
+ self.actions = Set.new Array(actions).map(&:to_s)
9
+ self.resource = resource
10
+ self.all_actions = false
11
+ end
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
+ self.roles = self.roles.merge(roles.flatten.compact.map(&:to_s))
21
+ self
22
+ end
23
+
24
+ # allow(*roles).on(resource).to_access(*actions)
25
+ def on(resource)
26
+ self.resource = resource
27
+ self
28
+ end
29
+
30
+ # allow(*roles).to_access *actions
31
+ def to_access(*actions)
32
+ self.actions = self.actions.merge actions.flatten.compact.map(&:to_s)
33
+ end
34
+
35
+ # allow role access to all actions
36
+ # allow(*roles).to_all
37
+ def to_all
38
+ self.all_actions = true
39
+ end
40
+
41
+ def allow?(current_roles, action)
42
+ action?(action) && role?(current_roles)
43
+ end
44
+
45
+ def allowed_roles(current_roles, action)
46
+ return [] unless action?(action)
47
+ match_roles(current_roles)
48
+ end
49
+
50
+ def role?(check_roles)
51
+ Array(check_roles).any? { |check_role| allowed_role?(check_role) }
52
+ end
53
+
54
+ def action?(check_actions)
55
+ check_actions = Set.new Array(check_actions).map(&:to_s)
56
+ all_actions? || !(check_actions & actions).empty?
57
+ end
58
+
59
+ def all_actions?
60
+ !!all_actions
61
+ end
62
+
63
+ private def allowed_resource?(check_role, required_resource)
64
+ return false unless check_role.respond_to?(:resource?)
65
+
66
+ required_resources = type_id_resource?(required_resource) ? [required_resource] : Array(required_resource)
67
+ required_resources.any? { |r| check_role.resource?(r) }
68
+ end
69
+
70
+ private def type_id_resource?(required_resource)
71
+ required_resource.is_a?(Array) && %w(String Symbol).include?(required_resource.first.class.name)
72
+ end
73
+
74
+ protected
75
+ attr_writer :roles
76
+ attr_writer :actions
77
+ attr_accessor :all_actions
78
+ attr_accessor :resource
79
+
80
+ private def match_roles(check_roles)
81
+ Array(check_roles).select do |check_role|
82
+ allowed_role?(check_role)
83
+ end
84
+ end
85
+
86
+ private def allowed_role?(role_object)
87
+ role_string = role_object.respond_to?(:to_role_string) ? role_object.to_role_string : role_object.to_s
88
+
89
+ roles.include?(role_string.to_s)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,51 @@
1
+ require 'forwardable'
2
+ require 'rolypoly/role_scope'
3
+
4
+ module Rolypoly
5
+ class RoleScopes
6
+ extend Forwardable
7
+ include Enumerable
8
+
9
+ def_delegators :build_role_scope, :allow, :on
10
+ def_delegators :@role_scopes, :clear, :each, :empty?
11
+
12
+ def initialize(role_scopes = [])
13
+ @role_scopes = Array(role_scopes)
14
+ end
15
+
16
+ def initialize_copy(other)
17
+ @role_scopes = @role_scopes.map(&:dup)
18
+ end
19
+
20
+ def allowed_roles(user_role_objects, action)
21
+ return [] if empty?
22
+
23
+ reduce([]) do |allowed_role_objects, role_scope|
24
+ allowed_role_objects | role_scope.allowed_roles(user_role_objects, action)
25
+ end
26
+ end
27
+
28
+ def scope_hash(user_role_objects)
29
+ actions.each_with_object({}) do |action, memo|
30
+ roles = allowed_roles(user_role_objects, action)
31
+ ids = roles.map(&:resource_id).compact.uniq
32
+ memo[action] = ids if ids.any?
33
+ memo
34
+ end
35
+ end
36
+
37
+ def all_access?(current_user_roles)
38
+ any? { |role_scope| role_scope.allow?(current_user_roles, nil) }
39
+ end
40
+
41
+ def actions
42
+ map(&:actions).each_with_object(Set.new) { |action_set, memo| memo.merge(action_set) }
43
+ end
44
+
45
+ private def build_role_scope(roles = nil, actions = nil, resource = nil)
46
+ new_role_scope = Rolypoly::RoleScope.new(roles, actions, resource)
47
+ @role_scopes << new_role_scope
48
+ new_role_scope
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module Rolypoly
2
- VERSION = "1.0.4"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -189,5 +189,47 @@ module Rolypoly
189
189
  end
190
190
 
191
191
  end
192
+
193
+ context 'allowed_roles' do
194
+ let(:roles) do
195
+ [double('admin', to_role_string: 'admin', resource?: nil),
196
+ double('org 1 admin', resource_id: '1', resource_type: 'Organization', to_role_string: 'org_admin', resource?: nil),
197
+ double('org 2 admin', resource_id: '2', resource_type: 'Organization', to_role_string: 'org_admin', resource?: nil),
198
+ double('tenant admin', resource_id: '3', resource_type: 'Tenant', to_role_string: 'org_admin', resource?: nil)]
199
+ end
200
+
201
+ context 'with unscoped gatekeeper' do
202
+ subject { described_class.new(['admin'], ['index']) }
203
+
204
+ it 'returns the unscoped role' do
205
+ expect(subject.allowed_roles(roles, 'index')).to eq([roles[0]])
206
+ end
207
+ end
208
+
209
+ context 'with resource-scoped gatekeeper' do
210
+ subject { described_class.new(['org_admin'], ['index'], :organization) }
211
+
212
+ it 'returns no roles' do
213
+ expect(subject.allowed_roles(roles, 'index')).to be_empty
214
+ end
215
+
216
+ it 'returns the matching role' do
217
+ query = ['Organization', '1']
218
+ expect(roles[1]).to receive(:resource?).with(query).and_return(true)
219
+ expect(subject.allowed_roles(roles, 'index', organization: query)).to eq([roles[1]])
220
+ end
221
+
222
+ it 'returns all roles with the same resource_type' do
223
+ query = { resource_type: 'Organization' }
224
+ expect(subject.allowed_roles(roles, 'index', organization: query)).to eq([roles[1], roles[2]])
225
+ end
226
+
227
+ it 'returns only the role matching the resource? call' do
228
+ query = { resource_id: '1', resource_type: 'Organization' }
229
+ expect(roles[1]).to receive(:resource?).with(query).and_return(true)
230
+ expect(subject.allowed_roles(roles, 'index', organization: query)).to eq([roles[1]])
231
+ end
232
+ end
233
+ end
192
234
  end
193
235
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rolypoly
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Phenow
8
8
  - Jake Waletzko
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-10-19 00:00:00.000000000 Z
12
+ date: 2020-10-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -58,9 +58,13 @@ files:
58
58
  - Rakefile
59
59
  - lib/rolypoly.rb
60
60
  - lib/rolypoly/controller_role_dsl.rb
61
+ - lib/rolypoly/index_role_dsl.rb
62
+ - lib/rolypoly/resource_index_query_builder.rb
61
63
  - lib/rolypoly/role_dsl.rb
62
64
  - lib/rolypoly/role_gatekeeper.rb
63
65
  - lib/rolypoly/role_gatekeepers.rb
66
+ - lib/rolypoly/role_scope.rb
67
+ - lib/rolypoly/role_scopes.rb
64
68
  - lib/rolypoly/version.rb
65
69
  - rolypoly.gemspec
66
70
  - spec/lib/rolypoly/controller_role_dsl_spec.rb
@@ -72,7 +76,7 @@ homepage: https://github.com/sportngin/rolypoly
72
76
  licenses:
73
77
  - MIT
74
78
  metadata: {}
75
- post_install_message:
79
+ post_install_message:
76
80
  rdoc_options: []
77
81
  require_paths:
78
82
  - lib
@@ -87,9 +91,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
91
  - !ruby/object:Gem::Version
88
92
  version: '0'
89
93
  requirements: []
90
- rubyforge_project:
91
- rubygems_version: 2.4.8
92
- signing_key:
94
+ rubygems_version: 3.0.3
95
+ signing_key:
93
96
  specification_version: 4
94
97
  summary: Tools for handling per-action and per-app Role authorization
95
98
  test_files: