permit 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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