pundit_roles 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73b78e648f853870e204510432f843c7a003b32f
4
- data.tar.gz: 5ca295739ef6482eeceffc70b2d49726bb672fcd
3
+ metadata.gz: 3adfc7de31b2f6a060d158f0106328f4bf86f68b
4
+ data.tar.gz: 95c046a4a1fa112a76823146c83421229f6aaf8b
5
5
  SHA512:
6
- metadata.gz: 9d37558263e616455f8fd07e155fa7529ff1e0a262c38978b6a4cc04a85f2dd91f42a17f68c8bfcda0012f3594ec2226393b644176ee81c79590103481436e53
7
- data.tar.gz: '084bd79f07ce70c8d583dc8c460b2fd6e22940854886dcf2970633c220be539ac98902c630db5575a1e71e981759d285ac6d68d4f1f5598af4ed828c5e337fee'
6
+ metadata.gz: 04a8d8ec524eecc18998b74c346200ff5e00b1c9c913f8cab8e292f80885f9c575f8cc1d8cce10586ade02a7621e4685b9725bee8a2ea0ffc1e445648adb07c9
7
+ data.tar.gz: 3bca92b85679f634a4317df69bd60e59dcb36c75d4fa009dcb5308e51dd5e6c5a79e1b85f4d6e5c6dc34257bb0e32d88d0de1779be6bddea28a0e6a28a1067eb
data/CHANGELOG.md CHANGED
@@ -14,3 +14,5 @@
14
14
 
15
15
  - `authorize` method has been renamed to `authorize!`
16
16
  - added support for limiting scopes, can be called with `policy_scope!`
17
+
18
+ ## 0.6.0
data/Gemfile CHANGED
@@ -8,6 +8,8 @@ gem 'activemodel'
8
8
  gem 'pundit', '~> 1.1.0'
9
9
 
10
10
  group :development, :test do
11
+ # gem 'simplecov', :require => false
12
+
11
13
  gem "bundler"
12
14
  gem "pry"
13
15
  gem "rake"
data/lib/pundit_roles.rb CHANGED
@@ -8,6 +8,7 @@ require 'pundit_roles/pundit'
8
8
  require 'pundit_roles/policy/base'
9
9
  require 'pundit'
10
10
 
11
+
11
12
  # Enhances Pundit with roles, allows definition of attribute and association level authorization
12
13
  module PunditRoles
13
14
  include Pundit
@@ -5,10 +5,10 @@ require_relative 'policy_defaults'
5
5
  module Policy
6
6
 
7
7
  # Base policy class to be extended by all other policies, authorizes users based on roles they fall into,
8
- # return a uniquely merged hash of permitted attributes and associations of each role the @user has.
8
+ # Can be used to get the attributes or scope of roles.
9
9
  #
10
10
  # @attr_reader user [Object] the user that initiated the action
11
- # @attr_reader resource [Object] the object we're checking permissions of
11
+ # @attr_reader resource [Object] the object we're checking @permissions of
12
12
  class Base
13
13
  extend Role
14
14
  include PolicyDefaults
@@ -17,10 +17,11 @@ module Policy
17
17
  def initialize(user, resource)
18
18
  @user = user
19
19
  @resource = resource
20
+ freeze
20
21
  end
21
22
 
22
23
  # Retrieves the permitted roles for the current query, checks if user is one or more of these roles
23
- # and return a hash of attributes and associations that the user has access to.
24
+ # and return a hash of attributes that the user has access to.
24
25
  #
25
26
  # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`)
26
27
  def resolve_query(query)
@@ -35,9 +36,7 @@ module Policy
35
36
  end
36
37
 
37
38
  current_roles = determine_current_roles(permitted_roles)
38
- return false unless current_roles.present?
39
-
40
- return options_or_merge(current_roles, permissions)
39
+ return unique_merge(current_roles, permissions)
41
40
  end
42
41
 
43
42
  # Retrieves the permitted roles for the current query and checks each role, until it finds one that
@@ -61,6 +60,14 @@ module Policy
61
60
  return instance_eval &scopes[current_roles[0]]
62
61
  end
63
62
 
63
+ def resolve_as_association(roles, actions)
64
+ permissions = self.class.permissions
65
+ default_roles = self.class::DEFAULT_ASSOCIATED_ROLES
66
+ associated_roles = roles.present? ? roles|default_roles : default_roles
67
+
68
+ return unique_merge(associated_roles, permissions, actions)
69
+ end
70
+
64
71
  private
65
72
 
66
73
  # Return the default :guest role if guest is present in permitted_roles. Return false otherwise
@@ -69,7 +76,14 @@ module Policy
69
76
  # @param permissions [Hash] unrefined hash of options defined by all permitted_for methods
70
77
  def handle_guest_options(permitted_roles, permissions)
71
78
  if permitted_roles.include? :guest
72
- return permissions[:guest].merge({roles: [:guest]})
79
+ guest_associations = self.class.role_associations[:guest] ? self.class.role_associations[:guest] : {}
80
+ return permissions[:guest].merge(
81
+ {roles:
82
+ {
83
+ for_current_model: [:guest],
84
+ for_associated_models: guest_associations
85
+ }
86
+ })
73
87
  end
74
88
  return false
75
89
  end
@@ -81,18 +95,47 @@ module Policy
81
95
  return false
82
96
  end
83
97
 
84
- # inspects the current_roles and returns the appropriate option
98
+ # Uniquely merge the options of all roles that the user fulfills
99
+ # Returns only the action(i.e. show, create) that was requested, by default this is all actions
85
100
  #
86
- # @param current_roles [Hash] roles that the current user fulfills
87
- # @param permissions [Hash] unrefined hash of options defined by all permitted_for methods
88
- def options_or_merge(current_roles, permissions)
89
- return false unless current_roles.present?
101
+ # @param roles [Hash] roles that the user fulfills
102
+ # @param permissions [Hash] the options for all roles
103
+ # @param requested_actions [Array] the requested actions
104
+ def unique_merge(roles, permissions, requested_actions = [:show, :create, :update, :save])
105
+ return false unless roles.present?
106
+ merged_hash = {attributes: {}, associations: {}, roles: {for_current_model: [], for_associated_models: {}}}
107
+
108
+ roles.each do |role|
109
+ merged_hash[:roles][:for_current_model] |= [role]
110
+ merged_hash[:roles][:for_associated_models] = merge_associated_roles(role, merged_hash[:roles][:for_associated_models])
90
111
 
91
- if current_roles.length == 1
92
- return permissions[current_roles[0]].merge({roles: [current_roles[0]]})
112
+ raise ArgumentError, "Role #{role} is not defined" unless permissions[role].present?
113
+
114
+ permissions[role].each do |type, permitted_actions|
115
+ actions = permitted_actions.slice(*requested_actions)
116
+ actions.each do |key, value|
117
+ unless merged_hash[type][key]
118
+ merged_hash[type][key] = []
119
+ end
120
+ merged_hash[type][key] |= value
121
+ end
122
+ end
93
123
  end
94
124
 
95
- return unique_merge(current_roles, permissions)
125
+ return merged_hash
126
+ end
127
+
128
+ def merge_associated_roles(role, merged_opts)
129
+ associated_roles = self.class.role_associations
130
+
131
+ return {} unless associated_roles[role].present?
132
+
133
+ associated_roles[role].each do |k, v|
134
+ assoc_role = {k => v}
135
+ merged_opts = merged_opts.merge(assoc_role){ | key, old, new | old | new}
136
+ end
137
+
138
+ return merged_opts
96
139
  end
97
140
 
98
141
  # Build an Array of the roles that the user fulfills.
@@ -114,27 +157,6 @@ module Policy
114
157
  return current_roles
115
158
  end
116
159
 
117
- # Uniquely merge the options of all roles that the user fulfills
118
- #
119
- # @param roles [Hash] roles that the user fulfills
120
- # @param permissions [Hash] the options for all roles
121
- def unique_merge(roles, permissions)
122
- merged_hash = {attributes: {}, associations: {}, roles: roles}
123
-
124
- roles.each do |role|
125
- permissions[role].each do |type, actions|
126
- actions.each do |key, value|
127
- unless merged_hash[type][key]
128
- merged_hash[type][key] = []
129
- end
130
- merged_hash[type][key] |= value
131
- end
132
- end
133
- end
134
-
135
- return merged_hash
136
- end
137
-
138
160
  # Helper method for testing the conditional of a role
139
161
  #
140
162
  # @param role [Symbol] the role to be tested
@@ -180,20 +202,6 @@ module Policy
180
202
  def _allowed_permission_types
181
203
  [Array, FalseClass, TrueClass]
182
204
  end
183
-
184
- # Scope class from Pundit, not used in this gem
185
- class Scope
186
- attr_reader :user, :scope
187
-
188
- def initialize(user, scope)
189
- @user = user
190
- @scope = scope
191
- end
192
-
193
- def resolve
194
- scope
195
- end
196
-
197
- end
198
205
  end
206
+
199
207
  end
@@ -25,6 +25,9 @@ module PolicyDefaults
25
25
  false
26
26
  end
27
27
 
28
+ # defaults to this when no associated_as roles have been provided. It is also merged to the provided roles
29
+ DEFAULT_ASSOCIATED_ROLES = []
30
+
28
31
  # restricted attributes for show
29
32
  RESTRICTED_SHOW_ATTRIBUTES = []
30
33
 
@@ -2,50 +2,71 @@ require_relative 'role/option_builder'
2
2
 
3
3
  # Extended by Policy::Base. Defines the methods necessary for declaring roles.
4
4
  module Role
5
-
6
5
  attr_accessor :permissions
6
+ attr_accessor :role_associations
7
7
  attr_accessor :scopes
8
8
 
9
9
  # Builds a new role by saving it into the #permissions class instance variable
10
- # Valid options are :attributes, :associations, :scope, :uses_db, :extend
10
+ # Valid options are :attributes, :associations, :associated_as, :scope
11
11
  #
12
12
  # @param *opts [Array] the roles, and the options which define the roles
13
13
  # @raise [ArgumentError] if the options are incorrectly defined, or no options are present
14
14
  def role(*opts)
15
- user_opts = opts.extract_options!.dup
16
- options = user_opts.slice(*_role_default_keys)
15
+ role_opts = opts.extract_options!.dup
16
+ options = role_opts.slice(*_role_default_keys)
17
17
 
18
18
  raise ArgumentError, 'Please provide at least one role' unless opts.present?
19
+ raise_if_options_are_invalid(options)
19
20
 
20
21
  @permissions = {} if @permissions.nil?
21
22
  @scopes = {} if @scopes.nil?
22
-
23
- options.each do |key, value|
24
- if value.present?
25
- expected = _role_option_validations[key]
26
- do_raise = true
27
- expected.each do |type|
28
- do_raise = false if value.is_a? type
29
- end
30
- raise ArgumentError, "Expected #{expected} for #{key}, got #{value.class}" if do_raise
31
- end
32
- end
23
+ @role_associations = {} if @role_associations.nil?
33
24
 
34
25
  opts.each do |role|
35
26
  raise ArgumentError, "Expected Symbol for #{role}, got #{role.class}" unless role.is_a? Symbol
27
+
28
+ if options[:associated_as].present?
29
+ build_associated_roles(role, options[:associated_as])
30
+ end
31
+
36
32
  @permissions[role] = OptionBuilder.new(self, options[:attributes], options[:associations], options[:scope]).permitted
37
33
  @scopes[role] = options[:scope]
38
34
  end
39
35
  end
40
36
 
37
+ # @api private
38
+ private def build_associated_roles(role, associated_as)
39
+ associated_as.each do |key, value|
40
+ unless associated_as[key].is_a? Array
41
+ associated_as[key] = [value]
42
+ end
43
+ end
44
+ @role_associations[role] = associated_as
45
+ end
46
+
47
+ # @api private
48
+ private def raise_if_options_are_invalid(options)
49
+ options.each do |key, value|
50
+ if value.present?
51
+ will_raise = true
52
+ _role_option_validations[key].each do |type|
53
+ will_raise = false if value.is_a? type
54
+ end
55
+ raise ArgumentError, "Expected #{expected} for #{key}, got #{value.class}" if will_raise
56
+ end
57
+ end
58
+ end
59
+
41
60
  # @api private
42
61
  private def _role_default_keys
43
- [:attributes, :associations, :scope, :uses_db, :extend]
62
+ [:attributes, :associations, :associated_as, :scope]
44
63
  end
45
64
 
46
65
  # @api private
47
66
  private def _role_option_validations
48
- {attributes: [Hash, Symbol], associations: [Hash, Symbol], scope: [Proc], uses_db: [Symbol], extend: [Symbol]}
67
+ {attributes: [Hash, Symbol], associations: [Hash, Symbol], associated_as: [Hash], scope: [Proc]}
49
68
  end
50
69
 
70
+
71
+
51
72
  end
@@ -50,10 +50,6 @@ module Role
50
50
  # @param options [Hash] unrefined hash containing either attributes or associations
51
51
  # @param type [String] the type of option to be built, can be 'attributes' or 'associations'
52
52
  def init_options(options, type)
53
- unless options.present?
54
- return {}
55
- end
56
-
57
53
  raise ArgumentError, "Permitted #{type}, if declared, must be declared as a Hash or Symbol, expected something along the lines of
58
54
  {show: [:id, :name], create: [:name], update: :all} or :all, got #{options}" unless options.is_a? Hash
59
55
 
@@ -61,21 +57,27 @@ module Role
61
57
  options.each do |key, value|
62
58
  raise ArgumentError, "Expected Symbol or Array, for #{key} attribute, got #{value} of kind #{value.class}" unless _permitted_value_types value
63
59
 
60
+ if key == :save
61
+ actions = [:create, :update]
62
+ else
63
+ actions = [key]
64
+ end
65
+
64
66
  if value.is_a? Symbol and value == :all
65
- parsed_options[key] = send("get_all_#{type}")
67
+ actions.each do |action|
68
+ parsed_options[action] = remove_restricted(action, type)
69
+ end
66
70
  next
67
71
  end
68
72
 
69
- # todo: This needs some rethinking
70
73
  if value.is_a? Array
71
- case value.first
72
- when :all_minus
73
- parsed_options[key] = send("get_all_#{type}") - (value - [value.first])
74
- else
75
- parsed_options[key] = value
74
+ actions.each do |action|
75
+ parsed_options[action] = [] unless parsed_options[action].present?
76
+ parsed_options[action] |= value
76
77
  end
77
78
  next
78
79
  end
80
+
79
81
  end
80
82
 
81
83
  return parsed_options
@@ -91,29 +93,30 @@ module Role
91
93
  parsed_options = {}
92
94
  case option
93
95
  when :show_all
94
- parsed_options[:show] = get_all(type)
96
+ parsed_options[:show] = remove_restricted(:show, type)
97
+ when :save_all
98
+ [:show, :create, :update].each do |action|
99
+ parsed_options[action] = remove_restricted(action, type)
100
+ end
95
101
  else
96
- of_type = option.to_s.gsub('_all', '').to_sym
97
- parsed_options[:show] = get_all(type)
98
- parsed_options[of_type] = get_all(type)
102
+ option_type = option.to_s.gsub('_all', '').to_sym
103
+ [:show, option_type].each do |action|
104
+ parsed_options[action] = remove_restricted(action, type)
105
+ end
99
106
  end
100
107
 
101
- return remove_restricted(parsed_options, type)
108
+ return parsed_options
102
109
  end
103
110
 
104
111
  # Remove restricted attributes declared in the #policy RESTRICTED_#{key}_#{type} constants
105
112
  #
106
- # @param obj [Hash] refined hash containing either attributes or associations
113
+ # @param action [Hash] the action we're fetch the restricted options for
107
114
  # @param type [String] the type of option to be built, can be 'attributes' or 'associations'
108
- def remove_restricted(obj, type)
109
- permitted_obj_values = {}
110
-
111
- obj.each do |key, value|
112
- restricted = "#{@policy}::RESTRICTED_#{key.upcase}_#{type.upcase}".constantize
113
- permitted_obj_values[key] = restricted.present? ? value - restricted : value
114
- end
115
+ def remove_restricted(action, type)
116
+ all_attributes = get_all(type)
117
+ restricted = "#{@policy}::RESTRICTED_#{action.upcase}_#{type.upcase}".constantize
115
118
 
116
- return permitted_obj_values
119
+ return restricted.present? ? all_attributes - restricted : all_attributes
117
120
  end
118
121
 
119
122
  # Returns all attributes of a record or scope defined in the #policy
@@ -130,7 +133,8 @@ module Role
130
133
  begin
131
134
  send("get_all_#{type}")
132
135
  rescue NameError => e
133
- raise ArgumentError, "#{@policy} does not have a corresponding model: #{@policy.to_s.gsub('Policy', '')}, implicit declarations are not allowed => #{e.message}"
136
+ raise ArgumentError, "#{@policy} does not seem to have a corresponding model: "+
137
+ "#{@policy.to_s.gsub('Policy', '')}, implicit declarations are not allowed => #{e.message}"
134
138
  end
135
139
  end
136
140
 
@@ -1,57 +1,99 @@
1
+ require 'pundit_roles/pundit_associations'
2
+ require 'pundit_roles/pundit_selectors'
3
+
1
4
  # Contains the overwritten #authorize method
2
5
  module PunditOverwrite
6
+ include PunditAssociations
7
+ include PunditSelectors
3
8
 
4
9
  # A modified version of Pundit's default authorization. Returns a hash of permitted attributes or raises exception
5
10
  # it the user is not authorized
6
11
  #
7
- # @param resource [Object] the object we're checking permissions of
8
- # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
9
- # If omitted then this defaults to the Rails controller action name.
12
+ # @param resource [Object] the object we're checking @permitted_attributes of
13
+ # @param opts [Hash] options for scopes: query, associations
14
+ # query: the method which returns the permissions,
15
+ # If omitted then this defaults to the Rails controller action name.
16
+ # associations: associations to authorize, defaults to []
10
17
  # @raise [NotAuthorizedError] if the given query method returned false
11
- # @return [Object, Hash] Returns the permissions hash or the resource
12
- def authorize!(resource, query = nil)
13
- query ||= params[:action].to_s + '?'
18
+ # @return [Object, Hash] Returns the @permitted_attributes hash or the resource
19
+ def authorize!(resource, opts = {query: nil, associations: []})
20
+ opts[:query] ||= params[:action].to_s + '?'
14
21
 
15
22
  @_pundit_policy_authorized = true
16
23
 
24
+ @pundit_current_options = {
25
+ primary_resource: resource.is_a?(Class) ? resource : resource.class,
26
+ current_query: opts[:query]
27
+ }
28
+
17
29
  policy = policy(resource)
18
- permitted_records = policy.resolve_query(query)
30
+ primary_permission = policy.resolve_query(opts[:query])
31
+
32
+ unless primary_permission
33
+ raise_not_authorized(resource)
34
+ end
35
+
36
+ if primary_permission.is_a? TrueClass
37
+ return resource
38
+ end
39
+
40
+ @pundit_primary_permissions = primary_permission
41
+
42
+ primary_resource_identifier = @pundit_current_options[:primary_resource].name.underscore.to_sym
43
+ @pundit_attribute_lists = {
44
+ show: {primary_resource_identifier => primary_show_attributes},
45
+ create: [*primary_create_attributes],
46
+ update: [*primary_update_attributes]
47
+ }
48
+ @pundit_permission_table = {}
49
+ @pundit_permitted_associations = {show: [], create: [], update: []}
19
50
 
20
- return determine_action(resource, query, policy, permitted_records)
51
+ if opts[:associations].present?
52
+ authorize_associations!(opts)
53
+ end
54
+
55
+ return @pundit_primary_permissions
21
56
  end
22
57
 
23
58
  # Returns the permitted scope or raises exception
24
59
  #
25
- # @param resource [Object] the object we're checking permissions of
26
- # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`).
27
- # If omitted then this defaults to the Rails controller action name.
60
+ # @param resource [Object] the object we're checking @permitted_attributes of
61
+ # @param opts [Hash] options for scopes: query, associations
62
+ # query: the method which returns the permissions,
63
+ # If omitted then this defaults to the Rails controller action name.
64
+ # associations: associations to scope, defaults to []
28
65
  # @raise [NotAuthorizedError] if the given query method returned false
29
- # @return [Object, ActiveRecord::Association] Returns the permissions hash or the resource
30
- def policy_scope!(resource, query = nil)
31
- query ||= params[:action].to_s + '?'
66
+ # @return [Object, ActiveRecord::Association] Returns the @permitted_attributes hash or the resource
67
+ def policy_scope!(resource, opts= {query: nil, associations: []})
68
+ opts[:query] ||= params[:action].to_s + '?'
32
69
 
33
70
  @_pundit_policy_scoped = true
34
71
 
35
- policy = policy(resource)
36
- permitted_scope = policy.resolve_scope(query)
37
-
38
- return determine_action(resource, query, policy, permitted_scope)
39
- end
72
+ @pundit_current_options = {
73
+ primary_resource: resource.is_a?(Class) ? resource : resource.class,
74
+ current_query: opts[:query]
75
+ }
40
76
 
41
- private
77
+ policy = policy(resource)
78
+ permitted_scope = policy.resolve_scope(opts[:query])
42
79
 
43
- # @api private
44
- def determine_action(resource, query, policy, permitted)
45
- unless permitted
46
- raise Pundit::NotAuthorizedError, query: query, record: resource, policy: policy
80
+ unless permitted_scope
81
+ raise_not_authorized(resource)
47
82
  end
48
83
 
49
- if permitted.is_a? TrueClass
84
+ if permitted_scope.is_a? TrueClass
50
85
  return resource
51
86
  end
52
87
 
53
- return permitted
88
+ return permitted_scope
89
+ end
90
+
91
+ def raise_not_authorized(record)
92
+ raise Pundit::NotAuthorizedError,
93
+ query: @pundit_current_options[:current_query],
94
+ record: record
54
95
  end
96
+
55
97
  end
56
98
 
57
99
  # Prepends the PunditOverwrite to Pundit, in order to overwrite the default Pundit #authorize method
@@ -0,0 +1,186 @@
1
+ # Module containing the methods to authorize associations
2
+ module PunditAssociations
3
+
4
+ # authorizes associations for the primary record
5
+ #
6
+ # @param opts [Hash]
7
+ # query: the method which returns the permissions,
8
+ # If omitted then this defaults to the Rails controller action name.
9
+ # associations: associations to authorize, defaults to []
10
+ def authorize_associations!(opts = {query: nil, associations: []})
11
+ raise ArgumentError, 'You must first call authorize!' unless @pundit_primary_permissions.present?
12
+
13
+ opts[:query] ||= params[:action].to_s + '?'
14
+
15
+ @pundit_requested_associations = Array.new(opts[:associations])
16
+ @pundit_allowed_associations = []
17
+
18
+ handle_associations(
19
+ @pundit_current_options[:primary_resource],
20
+ @pundit_requested_associations,
21
+ @pundit_primary_permissions,
22
+ @pundit_allowed_associations
23
+ )
24
+
25
+ [:show, :create, :update].each do |type|
26
+ determine_permitted_associations(
27
+ @pundit_allowed_associations,
28
+ @pundit_primary_permissions,
29
+ @pundit_permitted_associations[type],
30
+ type
31
+ )
32
+ end
33
+
34
+ @pundit_attribute_lists[:show].merge!(association_show_attributes)
35
+
36
+ [:create, :update].each do |type|
37
+ determine_save_permissions(
38
+ @pundit_permitted_associations[type],
39
+ @pundit_attribute_lists[type],
40
+ type
41
+ )
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # @api private
48
+ def handle_associations(record_class, requested_assoc, pundit_permission, permitted_assoc)
49
+ permitted_actions = format_association_list(pundit_permission[:associations])
50
+
51
+ requested_assoc.each do |assoc|
52
+ if assoc.is_a? Symbol or assoc.is_a? String
53
+ unless permitted_actions.keys.map(&:to_sym).include? assoc.to_sym
54
+ next
55
+ end
56
+
57
+ assoc_constant = get_assoc_constant(record_class, assoc)
58
+ fetch_assoc_policy(
59
+ assoc_constant,
60
+ assoc,
61
+ pundit_permission[:roles][:for_associated_models][assoc],
62
+ permitted_actions[assoc]
63
+ )
64
+ permitted_assoc << assoc
65
+
66
+ elsif assoc.is_a? Hash
67
+ assoc.each do |current_assoc, value|
68
+ unless permitted_actions.keys.map(&:to_sym).include? current_assoc.to_sym
69
+ next
70
+ end
71
+
72
+ assoc_constant = get_assoc_constant(record_class, current_assoc)
73
+ fetch_assoc_policy(
74
+ assoc_constant,
75
+ current_assoc,
76
+ pundit_permission[:roles][:for_associated_models][current_assoc],
77
+ permitted_actions[current_assoc]
78
+ )
79
+ permitted_assoc << {current_assoc => []}
80
+ handle_associations(
81
+ assoc_constant,
82
+ value,
83
+ @pundit_permission_table[current_assoc],
84
+ permitted_assoc.last[current_assoc]
85
+ )
86
+ end
87
+ else
88
+ raise ArgumentError, "Invalid association parameter, expected one of #{_valid_assoc_opts}, "+
89
+ "got #{assoc} of class #{assoc.class}"
90
+ end
91
+ end
92
+ end
93
+
94
+ # @api private
95
+ def fetch_assoc_policy(assoc_constant, association, associated_roles, actions)
96
+ assoc_policy = policy(assoc_constant)
97
+ assoc_permission = assoc_policy.resolve_as_association(associated_roles, actions)
98
+
99
+ unless assoc_permission
100
+ raise_not_authorized(assoc_constant)
101
+ end
102
+
103
+ @pundit_permission_table[association] = assoc_permission
104
+ end
105
+
106
+ # @api private
107
+ def format_association_list(assoc)
108
+ permitted_actions = {}
109
+ assoc.each do |key, associations|
110
+ associations.each do |ass|
111
+ permitted_actions[ass] = [] unless permitted_actions[ass].present?
112
+ permitted_actions[ass] |= [key]
113
+ end
114
+ end
115
+
116
+ return permitted_actions
117
+ end
118
+
119
+ # @api private
120
+ def determine_permitted_associations(requested_assoc, pundit_permission, permitted_opts, type)
121
+ permitted_actions = pundit_permission[:associations][type]
122
+
123
+ requested_assoc.each do |assoc|
124
+ if assoc.is_a? Symbol or assoc.is_a? String
125
+ if permitted_actions and permitted_actions.include? assoc
126
+ permitted_opts << assoc
127
+ end
128
+ elsif assoc.is_a? Hash
129
+ assoc.each do |current_assoc, value|
130
+ if permitted_actions and permitted_actions.include? current_assoc
131
+ permitted_opts << {current_assoc => []}
132
+
133
+ determine_permitted_associations(
134
+ value,
135
+ @pundit_permission_table[current_assoc],
136
+ permitted_opts.last[current_assoc],
137
+ type
138
+ )
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ # @api private
146
+ def determine_save_permissions(permitted_assoc, save_attributes, type)
147
+ permitted_assoc.each do |assoc|
148
+ if assoc.is_a? Symbol or assoc.is_a? String
149
+ assoc_sym = "#{assoc}_attributes".to_sym
150
+ save_attributes << {assoc_sym => @pundit_permission_table[assoc][:attributes][type]}
151
+ elsif assoc.is_a? Hash
152
+ assoc.each do |current_assoc, value|
153
+ assoc_sym = "#{current_assoc}_attributes".to_sym
154
+ save_attributes << {assoc_sym => @pundit_permission_table[current_assoc][:attributes][type]}
155
+
156
+ determine_save_permissions(
157
+ value,
158
+ save_attributes.last[assoc_sym],
159
+ type
160
+ )
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ # @api private
167
+ def get_assoc_constant(record_class, assoc)
168
+ begin
169
+ return assoc.to_s.classify.constantize
170
+ rescue NameError
171
+ assoc_aliases = record_class.reflect_on_all_associations.map{|ass| {ass.name => ass.class_name}}.reduce Hash.new, :merge
172
+
173
+ if assoc_aliases.keys.include? assoc
174
+ return assoc_aliases[assoc].constantize
175
+ else
176
+ raise NameError, "Could not find associated class #{assoc.to_s.classify}, and #{record_class}"+
177
+ "does not include any associations named #{assoc}"
178
+ end
179
+ end
180
+ end
181
+
182
+ # @api private
183
+ def _valid_assoc_opts
184
+ [Hash, String, Symbol]
185
+ end
186
+ end
@@ -0,0 +1,152 @@
1
+ # Module containing selectors for various authorized attributes, can be accessed as, ex: permitted_show_attributes
2
+ module PunditSelectors
3
+
4
+ # returns the permission hash for the primary model
5
+ def permissions
6
+ @pundit_primary_permissions
7
+ end
8
+
9
+ # returns the formatted attributes for :show, :create and :update, ready to plug-and-play
10
+ def attribute_permissions
11
+ @pundit_attribute_lists
12
+ end
13
+
14
+ # returns the permitted associations in the form of [Array] -> [{:posts => {:comments => [:author]}}, :settings]
15
+ def permitted_associations
16
+ @pundit_permitted_associations
17
+ end
18
+
19
+ # returns the permission hashes of permitted associations, ex: {:posts => {:attributes => {:show => [:text]}, :associations => {:show => [:comments]}}}
20
+ def association_permissions
21
+ @pundit_permission_table
22
+ end
23
+
24
+ def permitted_show_attributes
25
+ @pundit_attribute_lists[:show]
26
+ end
27
+
28
+ def permitted_create_attributes
29
+ @pundit_attribute_lists[:create]
30
+ end
31
+
32
+ def permitted_update_attributes
33
+ @pundit_attribute_lists[:update]
34
+ end
35
+
36
+ def permitted_show_associations
37
+ @pundit_permitted_associations[:show]
38
+ end
39
+
40
+ def permitted_create_associations
41
+ @pundit_permitted_associations[:create]
42
+ end
43
+
44
+ def permitted_update_associations
45
+ @pundit_permitted_associations[:update]
46
+ end
47
+
48
+ # returns the permitted show attributes of the primary model
49
+ def primary_show_attributes
50
+ @pundit_primary_permissions[:attributes][:show]
51
+ end
52
+
53
+ # returns the permitted create attributes of the primary model
54
+ def primary_create_attributes
55
+ @pundit_primary_permissions[:attributes][:create]
56
+ end
57
+
58
+ # returns the permitted update attributes of the primary model
59
+ def primary_update_attributes
60
+ @pundit_primary_permissions[:attributes][:update]
61
+ end
62
+
63
+ # returns the permitted show associations of the primary model
64
+ def primary_show_associations
65
+ @pundit_primary_permissions[:associations][:show]
66
+ end
67
+
68
+ # returns the permitted create associations of the primary model
69
+ def primary_create_associations
70
+ @pundit_primary_permissions[:associations][:create]
71
+ end
72
+
73
+ # returns the permitted update associations of the primary model
74
+ def primary_update_associations
75
+ @pundit_primary_permissions[:associations][:update]
76
+ end
77
+
78
+ # returns the permitted show attributes of the associated models
79
+ def association_show_attributes
80
+ return {} unless @pundit_permission_table
81
+ associated_stuff = {}
82
+ @pundit_permission_table.each do |role, action|
83
+ associated_stuff[role] = action[:attributes].slice(:show)[:show]
84
+ end
85
+ return associated_stuff
86
+ end
87
+
88
+ # returns the permitted create attributes of the associated models
89
+ def association_create_attributes
90
+ return {} unless @pundit_permission_table
91
+ associated_stuff = {}
92
+ @pundit_permission_table.each do |role, action|
93
+ associated_stuff[role] = action[:attributes].slice(:create)[:create]
94
+ end
95
+ return associated_stuff
96
+ end
97
+
98
+ # returns the permitted update attributes of the associated models
99
+ def association_update_attributes
100
+ return {} unless @pundit_permission_table
101
+ associated_stuff = {}
102
+ @pundit_permission_table.each do |role, action|
103
+ associated_stuff[role] = action[:attributes].slice(:update)[:update]
104
+ end
105
+ return associated_stuff
106
+ end
107
+
108
+ # returns the permitted show associations of the associated models
109
+ def association_show_associations
110
+ return {} unless @pundit_permission_table
111
+ associated_stuff = {}
112
+ @pundit_permission_table.each do |role, action|
113
+ associated_stuff[role] = action[:associations].slice(:show)[:show]
114
+ end
115
+ return associated_stuff
116
+ end
117
+
118
+ # returns the permitted create associations of the associated models
119
+ def association_create_associations
120
+ return {} unless @pundit_permission_table
121
+ associated_stuff = {}
122
+ @pundit_permission_table.each do |role, action|
123
+ associated_stuff[role] = action[:associations].slice(:create)[:create]
124
+ end
125
+ return associated_stuff
126
+ end
127
+
128
+ # returns the permitted update associations of the associated models
129
+ def association_update_associations
130
+ return {} unless @pundit_permission_table
131
+ associated_stuff = {}
132
+ @pundit_permission_table.each do |role, action|
133
+ associated_stuff[role] = action[:associations].slice(:update)[:update]
134
+ end
135
+ return associated_stuff
136
+ end
137
+
138
+ #
139
+ # # @api private
140
+ # def build_next_opts(assoc_opts, save_attributes, assoc, assoc_index)
141
+ # next_opts = {}
142
+ # [:show, :create, :update].each do |type|
143
+ # next_opts[type] = assoc_opts[type][assoc_index].present? ? assoc_opts[type][assoc_index][assoc] : nil
144
+ # end
145
+ #
146
+ # [:create, :update].each do |type|
147
+ # next_opts[type] = save_attributes[type].is_a?(Hash) ? save_attributes[type].values.last : nil
148
+ # end
149
+ #
150
+ # return next_opts
151
+ # end
152
+ end
@@ -1,3 +1,3 @@
1
1
  module PunditRoles
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pundit_roles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Balogh
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-08 00:00:00.000000000 Z
11
+ date: 2018-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -62,6 +62,8 @@ files:
62
62
  - lib/pundit_roles/policy/role.rb
63
63
  - lib/pundit_roles/policy/role/option_builder.rb
64
64
  - lib/pundit_roles/pundit.rb
65
+ - lib/pundit_roles/pundit_associations.rb
66
+ - lib/pundit_roles/pundit_selectors.rb
65
67
  - lib/pundit_roles/version.rb
66
68
  - pundit_roles.gemspec
67
69
  homepage: https://github.com/StairwayB/pundit_roles