permit 0.9.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.
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
- ---
2
- :major: 0
3
- :minor: 9
1
+ ---
2
+ :minor: 0
4
3
  :patch: 0
5
- :build:
4
+ :major: 1
5
+ :build:
@@ -4,17 +4,19 @@ module Permit
4
4
  # associations to help with querying for common cases. Some of these methods
5
5
  # do not show up in the documentation because they are dynamically created
6
6
  # with class_eval so that they can be explicit to the models you use for the
7
- # Person and Role models.
7
+ # Person and Role models. See the documentation for {#method_missing} for
8
+ # additionally created methods.
8
9
  module AssociationExtensions
9
10
  include Permit::Support
10
11
 
11
- # Finds all authorizations for the given resource
12
+ # Finds all authorizations for the given resources
12
13
  #
13
- # @param [permit_authorizable, nil, :any] resource the resource to find
14
- # authorizations for. :any may be given to find matches for any resource.
14
+ # @param [permit_authorizable, nil, :any, <permit_authorizable, nil>]
15
+ # resource the resources to find authorizations for. :any may be given
16
+ # to find matches for any resource.
15
17
  # @return [<permit_authorization>] the authorizations found for the resource.
16
- def for(resource)
17
- conditions = authorization_conditions(nil, resource)
18
+ def for(resources)
19
+ conditions = authorization_conditions(nil, resources)
18
20
  find(:all, :conditions => conditions)
19
21
  end
20
22
 
@@ -35,8 +37,8 @@ module Permit
35
37
  # @param [permit_role, String, Symbol, <permit_role, String, Symbol>]
36
38
  # roles the roles to find authorizations for.
37
39
  # @return [<permit_authorization>] the authorizations found for the resource and role(s)
38
- def for_resource_as(resource, roles)
39
- conditions = authorization_conditions(roles, resource)
40
+ def for_resources_as(resources, roles)
41
+ conditions = authorization_conditions(roles, resources)
40
42
  find(:all, :conditions => conditions)
41
43
  end
42
44
 
@@ -50,38 +52,53 @@ module Permit
50
52
  as(roles).collect(&:resource).uniq
51
53
  end
52
54
 
55
+ # Defines three methods used for getting your subject models for a
56
+ # resource, or as various roles, as well as role models for a given
57
+ # resource.
58
+ #
59
+ # @overload people_for(resources)
60
+ # Finds all of the subjects that have authorizations for the given
61
+ # resources. Where "people" is the plural name of your subject model.
62
+ #
63
+ # @param [permit_authorizable, nil, :any, <permit_authorizable, nil>]
64
+ # resource the resources to find authorizations for. :any may be given
65
+ # to find matches for any resource.
66
+ # @return [<permit_person>] a unique list of the people with
67
+ # authorizations for the resource.
68
+ #
69
+ # @overload people_as(roles)
70
+ # Finds all of the subjects that have authorizations for the given
71
+ # role(s). Where "people" is the plural name of your subject model.
72
+ #
73
+ # @param [permit_role, String, Symbol, <permit_role, String, Symbol>]
74
+ # roles the roles to find authorizations for.
75
+ # @return [<permit_person>] a unique list of the people with
76
+ # authorizations for the role(s).
77
+ #
78
+ # @overload roles_for(resources)
79
+ # Finds all of the roles authorized for the given resources. Where
80
+ # "roles" is the plural name of your role model.
81
+ #
82
+ # @param [permit_authorizable, nil, :any, <permit_authorizable, nil>]
83
+ # resource the resources to find authorizations for. :any may be given
84
+ # to find matches for any resource.
85
+ # @return [<permit_role>] a unique list of roles authorized for the
86
+ # resource.
87
+ def method_missing(*args, &block); super; end
88
+
53
89
  def self.extended(klass)
54
90
  class_eval <<-END
55
- # Finds all of the people that have authorizations for the given resource.
56
- #
57
- # @param [permit_authorizable, nil, :any] resource the resource to find
58
- # authorizations for. :any may be given to find matches for any resource.
59
- # @return [<permit_person>] a unique list of the people with
60
- # authorizations for the resource.
61
- def #{Permit::Config.person_class.plural_class_symbol.to_s}_for(resource)
62
- self.for(resource).collect(&:#{Permit::Config.person_class.class_symbol.to_s}).uniq
91
+ def #{Permit::Config.person_class.plural_class_symbol.to_s}_for(resources)
92
+ self.for(resources).collect(&:#{Permit::Config.person_class.class_symbol.to_s}).uniq
63
93
  end
64
94
 
65
- # Finds all of the people that have authorizations for the given role(s).
66
- #
67
- # @param [permit_role, String, Symbol, <permit_role, String, Symbol>]
68
- # roles the roles to find authorizations for.
69
- # @return [<permit_person>] a unique list of the people with
70
- # authorizations for the role(s).
71
95
  def #{Permit::Config.person_class.plural_class_symbol.to_s}_as(roles)
72
96
  as(roles).collect(&:#{Permit::Config.person_class.class_symbol.to_s}).uniq
73
97
  end
74
98
 
75
- # Finds all of the roles authorized for the given resource.
76
- #
77
- # @param [permit_authorizable, nil, :any] resource the resource to find
78
- # authorizations for. :any may be given to find matches for any resource.
79
- # @return [<permit_role>] a unique list of roles authorized for the
80
- # resource.
81
- def #{Permit::Config.role_class.plural_class_symbol.to_s}_for(resource)
82
- self.for(resource).collect(&:#{Permit::Config.role_class.class_symbol.to_s}).uniq
99
+ def #{Permit::Config.role_class.plural_class_symbol.to_s}_for(resources)
100
+ self.for(resources).collect(&:#{Permit::Config.role_class.class_symbol.to_s}).uniq
83
101
  end
84
-
85
102
  END
86
103
  end
87
104
  end
data/lib/models/person.rb CHANGED
@@ -24,15 +24,15 @@ module Permit
24
24
  #
25
25
  # @param [Role, String, Symbol, <Role, String, Symbol>] roles the roles
26
26
  # to check for authorization on.
27
- # @param [Authorizable, nil, :any] resource the resource to check for
27
+ # @param [Authorizable, nil, :any, <Authorizable, nil>] resources the resources to check for
28
28
  # authorization on.
29
29
  # @return [true, false] true if the person is authorized on any of the a
30
30
  # roles, false otherwise.
31
- def authorized?(roles, resource)
31
+ def authorized?(roles, resources)
32
32
  permit_arrayify(roles).each do |r|
33
33
  role = get_role(r)
34
34
  next unless role
35
- conditions = authorization_conditions(role, resource)
35
+ conditions = authorization_conditions(role, resources)
36
36
  return true if permit_authorizations_proxy.exists?(conditions)
37
37
  end
38
38
  return false
@@ -43,16 +43,22 @@ module Permit
43
43
  #
44
44
  # @param [permit_role, String, Symbol, <permit_role, String, Symbol>]
45
45
  # roles the roles to check for authorization on.
46
- # @param [permit_authorizable, nil, :any] resource the resource to check for
46
+ # @param [permit_authorizable, nil, :any, <permit_authorizable, nil>] resources the resources to check for
47
47
  # authorization on.
48
48
  # @return [true, false] true if the person is authorized on all of the a
49
49
  # roles, false otherwise.
50
- def authorized_all?(roles, resource)
50
+ def authorized_all?(roles, resources)
51
51
  permit_arrayify(roles).each do |r|
52
52
  role = get_role(r)
53
53
  return false unless role
54
- conditions = authorization_conditions(role, resource)
55
- return false unless permit_authorizations_proxy.exists?(conditions)
54
+ conditions = authorization_conditions(role, resources)
55
+ if resources == :any
56
+ # No idea how many authz they should have. As long as they have
57
+ # something, that's good enough.
58
+ return false unless permit_authorizations_proxy.exists?(conditions)
59
+ else
60
+ return false unless permit_authorizations_proxy.count(:conditions => conditions) == permit_arrayify(resources).size
61
+ end
56
62
  end
57
63
  return true
58
64
  end
data/lib/permit.rb CHANGED
@@ -32,12 +32,12 @@ module Permit
32
32
  # of all classes that are authorizable to roles by having defined
33
33
  # +permit_authorizable+.
34
34
  class Config
35
- @@authorization_class, @@person_class, @@role_class = nil, nil, nil
36
- @@models_defined = false
37
- @@authorizable_classes = []
38
- @@controller_subject_method = nil
35
+ @authorization_class, @person_class, @role_class = nil, nil, nil
36
+ @authorizable_classes = []
37
+ @controller_subject_method = nil
39
38
 
40
- @@action_aliases = {
39
+
40
+ @action_aliases = {
41
41
  :create => [:new, :create],
42
42
  :update => [:edit, :update],
43
43
  :destroy => [:delete, :destroy],
@@ -48,44 +48,55 @@ module Permit
48
48
  # Indicates the response returned by {PermitRules#permitted?} when no rules
49
49
  # match. If set to +:allow+ then the person will be granted access. If set
50
50
  # to anything else, they will be denied.
51
- @@default_access = :deny
51
+ @default_access = :deny
52
52
 
53
53
  class << self
54
+ # Actions that when given to {PermitRules#allow}, and {PermitRules#deny}
55
+ # will be expanded into the actions given in the value array.
56
+ #
57
+ # Defaults to:
58
+ # {
59
+ # :create => [:new, :create],
60
+ # :update => [:edit, :update],
61
+ # :destroy => [:delete, :destroy],
62
+ # :read => [:index, :show],
63
+ # :write => [:new, :create, :edit, :update]
64
+ # }
65
+ attr_reader :action_aliases
66
+
54
67
  # The class that currently represents authorizations in the system, as set
55
68
  # by {set_core_models}.
56
- def authorization_class; @@authorization_class; end
69
+ attr_reader :authorization_class
57
70
  # The class that currently represents authorization subjects in the
58
71
  # system, as set by {set_core_models}.
59
- def person_class; @@person_class; end
72
+ attr_reader :person_class
60
73
  # The class that curretly represents roles in the system, as set by
61
74
  # {set_core_models}.
62
- def role_class; @@role_class; end
75
+ attr_reader :role_class
63
76
  # Classes that are marked as authorizable resources using
64
77
  # {Permit::Models::AuthorizableExtensions::AuthorizableClassMethods#permit_authorizable permit_authorizable}.
65
- def authorizable_classes; @@authorizable_classes; end
78
+ attr_reader :authorizable_classes
66
79
 
67
- # Actions that when given to {PermitRules#allow}, and {PermitRules#deny}
68
- # will be expanded into the actions given in the value array.
69
- def action_aliases; @@action_aliases; end
80
+ #def action_aliases; @@action_aliases; end
70
81
 
71
82
  # Indicates the response that PermitRules will take if no
72
83
  # authorizations match. If set to +:allow+ then a subject will be given
73
84
  # access unless denied. By default this is set to +:deny+
74
85
  #
75
86
  # @return the current default access.
76
- def default_access; @@default_access; end
87
+ def default_access; @default_access; end
77
88
 
78
89
  # Sets the response that PermitRules will use when no rules match.
79
90
  #
80
91
  # @param [:allow, :deny] access the default response to use.
81
- def default_access=(access); @@default_access = access; end
92
+ def default_access=(access); @default_access = access; end
82
93
 
83
94
  # The method to use to retrieve the current authorization subject when
84
95
  # rules are being evaluated. If nil, then the method will be inferred from
85
96
  # the subject set in the call to {set_core_models}.
86
97
  #
87
98
  # @return [Symbol, nil]
88
- def controller_subject_method; @@controller_subject_method; end
99
+ def controller_subject_method; @controller_subject_method; end
89
100
 
90
101
  # Sets the name of the method to use to retrieve the current subject
91
102
  # while checking authorizations. Set to nil, to infer the value from the
@@ -93,7 +104,7 @@ module Permit
93
104
  # authorizations are not being used.
94
105
  #
95
106
  # @param [nil, Symbol] method a symbol representing the method to use.
96
- def controller_subject_method=(method); @@controller_subject_method = method; end
107
+ def controller_subject_method=(method); @controller_subject_method = method; end
97
108
 
98
109
  # Sets the core authorization, person, and role models to be used for
99
110
  # named authorizations, and configures them with their respective permit_*
@@ -108,13 +119,13 @@ module Permit
108
119
  def set_core_models(authorization, person, role)
109
120
  #raise PermitConfigurationError, "Core models cannot be redefined." if @@models_defined
110
121
 
111
- @@authorization_class = authorization
112
- @@person_class = person
113
- @@role_class = role
122
+ @authorization_class = authorization
123
+ @person_class = person
124
+ @role_class = role
114
125
 
115
- @@authorization_class.send :permit_authorization
116
- @@person_class.send :permit_person
117
- @@role_class.send :permit_role
126
+ @authorization_class.send :permit_authorization
127
+ @person_class.send :permit_person
128
+ @role_class.send :permit_role
118
129
  end
119
130
 
120
131
  # Forces Permit to reload its core classes based off of those given in the
@@ -80,27 +80,78 @@ module Permit
80
80
  true
81
81
  end
82
82
 
83
- # Creates a PermitRule with the arguments that are given, and attempts to
84
- # match it based on the current person and binding context.
83
+ # Determines if a person is allowed access by evaluating rules for a
84
+ # controller/action, or for a custom rule.
85
85
  #
86
- # For information on the parameters for this method see
87
- # {PermitRule#initialize}.
86
+ # @overload allowed?(roles, options = {})
87
+ # Creates a PermitRule with the arguments that are given, and attempts
88
+ # to match it based on the current subject and binding context.
89
+ #
90
+ # For information on the parameters for this method see
91
+ # {PermitRule#initialize}.
92
+ #
93
+ # @return [Boolean] true if the rule matches, otherwise false.
94
+ #
95
+ # @overload allowed?(options)
96
+ # Attempts to evaluate the rules for the given action against the
97
+ # specified controller using the current subject, and binding context.
88
98
  #
89
- # @return [Boolean] true if the rule matches, otherwise false.
90
- def allowed?(roles, options = {})
91
- rule = PermitRule.new roles, options
92
- rule.matches? permit_authorization_subject, binding
99
+ # Keep in mind that the evaluation is performed using the binding of the
100
+ # current controller. Any instance variables that may normally be needed
101
+ # for the rules on another controller need to exist in the current
102
+ # controller.
103
+ #
104
+ # @param [Hash] options the controller/action to evaluate rules for.
105
+ # @option options [String, Symbol] controller the name of the controller to
106
+ # evaluate the rules from. If this is not given then the current
107
+ # controller is used. You may use the string syntax 'namespaced/teams'
108
+ # for a namespaced controller Namespaced::TeamsController.
109
+ # @option options [Symbol] action the action to evaluate rules for.
110
+ #
111
+ # @return [Boolean] true if the rule matches, otherwise false.
112
+ def allowed?(*args)
113
+ options = args.extract_options!
114
+ if options.has_key? :action
115
+ name = options[:controller]
116
+ klass = (name ? "#{name}_controller".camelize.constantize : self)
117
+ klass.permit_rules.permitted? permit_authorization_subject, options[:action], binding
118
+ else
119
+ rule = PermitRule.new args[0], options
120
+ rule.matches? permit_authorization_subject, binding
121
+ end
93
122
  end
94
123
 
95
- # Creates a PermitRule with the arguments that are given, and attempts to
96
- # match it based on the current person and binding context.
124
+ # Determines if a person is denied access by evaluating rules for a
125
+ # controller/action, or for a custom rule.
97
126
  #
98
- # For information on the parameters for this method see
99
- # {PermitRule#initialize}.
127
+ # @overload denied?(roles, options = {})
128
+ # Creates a PermitRule with the arguments that are given, and attempts
129
+ # to match it based on the current subject and binding context.
130
+ #
131
+ # For information on the parameters for this method see
132
+ # {PermitRule#initialize}.
133
+ #
134
+ # @return [Boolean] true if the rule does not match, otherwise false.
135
+ #
136
+ # @overload denied?(options)
137
+ # Attempts to evaluate the rules for the given action against the
138
+ # specified controller using the current subject, and binding context.
139
+ #
140
+ # Keep in mind that the evaluation is performed using the binding of the
141
+ # current controller. Any instance variables that may normally be needed
142
+ # for the rules on another controller need to exist in the current
143
+ # controller.
144
+ #
145
+ # @param [Hash] options the controller/action to evaluate rules for.
146
+ # @option options [String, Symbol] controller the name of the controller to
147
+ # evaluate the rules from. If this is not given then the current
148
+ # controller is used. You may use the string syntax 'namespaced/teams'
149
+ # for a namespaced controller Namespaced::TeamsController.
150
+ # @option options [Symbol] action the action to evaluate rules for.
100
151
  #
101
- # @return [Boolean] true if the rule does not match, otherwise false.
102
- def denied?(roles, options = {})
103
- !allowed? roles, options
152
+ # @return [Boolean] true if the subject is denied, otherwise false.
153
+ def denied?(*args)
154
+ !allowed? *args
104
155
  end
105
156
 
106
157
  # Shortcut for +current_person#authorized?+. If the current person is a
@@ -108,8 +159,8 @@ module Permit
108
159
  #
109
160
  # For information on the parameters for this method see
110
161
  # {Permit::Models::PersonExtensions::PersonInstanceMethods#authorized?}
111
- def authorized?(roles, resource)
112
- permit_authorization_subject.guest? ? false : permit_authorization_subject.authorized?(roles, resource)
162
+ def authorized?(roles, resources)
163
+ permit_authorization_subject.guest? ? false : permit_authorization_subject.authorized?(roles, resources)
113
164
  end
114
165
 
115
166
  private
@@ -1,11 +1,12 @@
1
1
  module Permit
2
+ # Defines an authorization rule to match against.
2
3
  class PermitRule
3
4
  include Permit::Support
4
5
 
5
6
  VALID_OPTION_KEYS = [:who, :that, :of, :on, :if, :unless]
6
7
  BUILTIN_ROLES = [:person, :guest, :everyone]
7
8
 
8
- attr_reader :roles, :target_var, :method, :if, :unless
9
+ attr_reader :roles, :target_vars, :method, :if, :unless
9
10
 
10
11
  # Creates a new PermitRule.
11
12
  #
@@ -35,8 +36,8 @@ module Permit
35
36
  # +is_owner()+, +is_owner?()+, +owner()+, +owner+, +owners.exist?()+. If
36
37
  # this option is given +:of+/+:on+ must also be given.
37
38
  # @option options [Symbol] :that alias for +:who+
38
- # @option options [Symbol] :of The name of the instance variable to use as
39
- # the target resource.
39
+ # @option options [Symbol, nil, :any, <Symbol, nil>] :of The name of the
40
+ # instance variable(s) to use as the target resource(s).
40
41
  #
41
42
  # In a dynamic authorization this is the object that will be tested using
42
43
  # the value of +:who+/+:that+.
@@ -46,12 +47,15 @@ module Permit
46
47
  # indicate a match if the person has one of the roles for any resource. If
47
48
  # not given, or set to +nil+, then the match will apply to a person that
48
49
  # has a matching role authorization for a nil resource.
49
- # @option options [Symbol] :on alias for +:of+
50
+ # @option options [Symbol, nil, :any, <Symbol, nil>] :on alias for +:of+
50
51
  # @option options [Symbol, String, Proc] :if code to evaluate at the end of the
51
- # match if it is still valid. If it returns false, the rule will not match.
52
- # @option options [Symbol, String, Proc] :unless code to evaluate at the end
53
- # of the match if it is still valid. If it returns true, the rule will not
54
- # match.
52
+ # match if it is still valid. If it returns false, the rule will not
53
+ # match. If a proc if given, it will be passed the current subject and
54
+ # binding. A method will be called without any arguments.
55
+ # @option options [Symbol, String, Proc] :unless code to evaluate at the end of
56
+ # the match if it is still valid. If it returns true, the rule will not
57
+ # match. If a proc if given, it will be passed the current subject and
58
+ # binding. A method will be called without any arguments.
55
59
  #
56
60
  # @raise [PermitConfigurationError] if the rule options are invalid.
57
61
  def initialize(roles, options = {})
@@ -62,7 +66,7 @@ module Permit
62
66
  validate_options options
63
67
 
64
68
  @method = options[:who] || options[:that]
65
- @target_var = options[:of] || options[:on]
69
+ @target_vars = permit_arrayify(options[:of] || options[:on]).uniq.freeze
66
70
 
67
71
  @if = options[:if]
68
72
  @unless = options[:unless]
@@ -83,7 +87,7 @@ module Permit
83
87
  has_named_authorizations? person, context_binding
84
88
  end
85
89
 
86
- passed_conditionals = matched ? passes_conditionals?(context_binding) : false
90
+ passed_conditionals = matched ? passes_conditionals?(person, context_binding) : false
87
91
  passed = matched && passed_conditionals
88
92
  return passed
89
93
  end
@@ -121,24 +125,36 @@ module Permit
121
125
 
122
126
  def has_named_authorizations?(person, context_binding)
123
127
  return false if person.guest?
124
- resource = case @target_var
125
- when nil then nil
126
- when :any then :any
127
- else get_resource(context_binding)
128
- end
129
- person.authorized? @roles, resource
128
+ resources = []
129
+ @target_vars.each do |var_name|
130
+ resources << case var_name
131
+ when nil then nil
132
+ when :any then (resources = :any and break)
133
+ else get_resource(var_name, context_binding)
134
+ end
135
+ end
136
+ person.authorized? @roles, resources
130
137
  end
131
138
 
132
139
  def has_dynamic_authorization?(person, context_binding)
133
140
  return false if person.guest?
134
- return true if @target_var.nil?
135
141
 
136
- resource = get_resource context_binding
137
142
  methods = determine_method_sequence @method
138
143
 
144
+ @target_vars.each do |var_name|
145
+ return true if var_name.nil?
146
+
147
+ resource = get_resource var_name, context_binding
148
+ return true if evaluate_dynamic_methods(var_name, resource, methods, person)
149
+ end
150
+
151
+ return false
152
+ end
153
+
154
+ def evaluate_dynamic_methods(var_name, resource, methods, person)
139
155
  methods.each do |name, type|
140
156
  next unless resource.respond_to? name
141
-
157
+
142
158
  case type
143
159
  when :method then return resource.send name, person
144
160
  when :getter then return resource.send(name) == person
@@ -148,7 +164,7 @@ module Permit
148
164
  end
149
165
 
150
166
  # Target didn't respond to any attempts. This would be a problem.
151
- raise PermitEvaluationError, "Target object ':#{@target_var}' evaluated as #{resource.inspect} did not respond to any of the following: #{methods.collect {|n,t| n}.join(', ')}"
167
+ raise PermitEvaluationError, "Target object ':#{var_name}' evaluated as #{resource.inspect} did not respond to any of the following: #{methods.collect {|n,t| n}.join(', ')}"
152
168
  end
153
169
 
154
170
  # is_owner - is_owner(), is_owner?(), owner?(), owner, owners.exists()
@@ -174,20 +190,25 @@ module Permit
174
190
  end
175
191
  end
176
192
 
177
- def get_resource(context_binding)
178
- eval "@#{@target_var.to_s}", context_binding
193
+ def get_resource(var, context_binding)
194
+ var_name = "@#{var.to_s}"
195
+ if eval(%Q{instance_variables.include? "#{var_name}"}, context_binding)
196
+ eval var_name, context_binding
197
+ else
198
+ raise PermitEvaluationError, "Target resource '#{var_name}' did not exist in the given context."
199
+ end
179
200
  end
180
201
 
181
- def passes_conditionals?(context_binding)
182
- return false unless eval_conditional @if, true, context_binding
183
- return false if eval_conditional @unless, false, context_binding
202
+ def passes_conditionals?(person, context_binding)
203
+ return false unless eval_conditional @if, true, person, context_binding
204
+ return false if eval_conditional @unless, false, person, context_binding
184
205
  true
185
206
  end
186
207
 
187
- def eval_conditional(condition, default, context_binding)
208
+ def eval_conditional(condition, default, person, context_binding)
188
209
  if condition
189
210
  condition = condition.to_s if Symbol===condition
190
- return (String===condition ? eval(condition, context_binding) : condition.call)
211
+ return (String===condition ? eval(condition, context_binding) : condition.call(person, context_binding))
191
212
  else
192
213
  return default
193
214
  end