| Module | Access::RequirePrivilege::ClassMethods |
| In: |
lib/access_require_privilege.rb
|
Returns blocks of attributes to be set before others in attributes=
The problem we‘re trying to solve here is, say:
Blog.create :owner => ..., :name => ...
where the name attribute is guarded by require_privilege :to_update. If :owner is not set first, the permission check will fail. So, we have to set up the access control keys before the other attributes.
(In general, it‘s possible for an attribute to be both settable and an access control key — imagine a "published" flag which governs read access for some users, and a "publish" privilege which allows an editor to set the attribute. In this case, we‘d want two blocks of "early privileges", one for the access control keys that determine whether you can publish, and one for the published flag itself, followed by the ordinary attributes).
# File lib/access_require_privilege.rb, line 498
498: def attribute_block_set_groups
499: [['owner', 'owner_id', 'owner_firm', 'owner_firm_id']]
500: end
Puts the operations on a list of known privileges for the class which are controlled by the RBAC system. Privileges will typically name an operation or set of operations.
The list may be retrieved by calling Klass.declared_privileges
# File lib/access_require_privilege.rb, line 46
46: def declare_privilege *args
47: args.each do |arg|
48: if !arg.is_a?( Symbol )
49: raise ArgumentError,
50: "Attempt to declare #{arg.inspect}, not a symbol, " +
51: "as a privilege"
52: end
53: @declared_privileges ||= DEFAULT_DECLARED_PRIVILEGES.dup
54: @declared_privileges << arg unless
55: @declared_privileges.include? arg
56: end
57: end
Returns an array of operations (as symbols) which have been declared as permissioned operations for this class, via declare_permission or require_privilege
# File lib/access_require_privilege.rb, line 63
63: def declared_privileges
64: @declared_privileges ||= DEFAULT_DECLARED_PRIVILEGES.dup
65: @declared_privileges
66: end
# File lib/access_require_privilege.rb, line 567
567: def dissociate_privilege( foreign_class_name, association_name )
568: reflected_privilege( :dissociate,
569: foreign_class_name + '#' + association_name.to_s )
570: end
Supported arguments are as for require_privilege (q.v.), but this function arranges that the permission check always fails. So, for instance,
never_permit_anyone :to_update_attribute => :name
would prevent anyone from changing the name of a record that had already been saved, and
never_permit_anyone :at_callback => :before_update
would keep any preexisting record from ever getting updated at all.
# File lib/access_require_privilege.rb, line 267
267: def never_permit_anyone key_args
268: valid_keys = [:to_update_attribute, :to_initialize_attribute,
269: :at_callback, :for_action]
270: key_args.each do |k,v|
271: unless valid_keys.include?( k )
272: raise ArgumentError,
273: "#{k.inspect} not a valid keyword for never_permit_anyone"
274: end
275: end
276: require_privilege_internal :forbidden_operation, key_args
277: end
Returns true if the user could ever create an object of this class.
Ordinarily checks two ways that permission could be denied:
If so, we require that the user has that required permission.
# File lib/access_require_privilege.rb, line 515
515: def permits_create?( user = User.current )
516:
517: # Check :before_create callback on this class, if any
518:
519: priv = self.callback_privilege( :before_create )
520:
521: unless priv.nil?
522: if !priv.is_a?( Array )
523: klass = self
524: else
525: association = class_for_associate( priv.last )
526: priv = priv.first
527: end
528:
529: return false unless user.could_ever?( priv, klass )
530: end
531:
532: # Check associates...
533:
534: reflect_on_all_associations( :belongs_to ).each do |assoc|
535:
536: foreign_key = assoc.primary_key_name.to_s # sigh...
537: column_desc = columns.detect { |col| col.name == foreign_key }
538:
539: if !column_desc.null
540: klass = class_for_associate( assoc.name )
541: assoc_priv = klass.associate_privilege( self.name, assoc.name )
542: return false if !assoc_priv.nil? &&
543: !user.could_ever?( assoc_priv, klass )
544: end
545:
546: end
547:
548: # All checks passed
549:
550: return true
551:
552: end
An abbreviation for common uses of +require_privilege :for_action+.
require_eponymous_privilege_to :create, :destroy
is just shorthand for
require_privilege :create, :for_action => :create require_privilege :destroy, :for_action => :destroy
# File lib/access_require_privilege.rb, line 402
402: def require_eponymous_privilege_to *args
403:
404: args.each do |arg|
405: callback = EVENT_CALLBACK_KEYS[ arg ]
406: if callback.nil?
407: raise ArgumentError,
408: "require_eponymous_privilege can't handle #{arg}"
409: end
410: require_privilege arg, :at_callback => callback
411: end
412:
413: end
‘require_privilege’ arranges to auto-generate wrapper code which will cause permissions to be checked before the model code performs certain actions.
The generated code usually checks for permissions declared on the object itself. However, :on_associated requests permission checks be performed on some associated object, named by an attribute. Thus for instance,
class Post
belongs_to :blog
require_privilege :edit_post, :on_associated => :blog,
:to_set_attribute => [:text]
...
end
will prohibit setting the text of a post unless the user has an ‘edit_post’ permission on the associated blog. If the association isn‘t set up, all such checks fail. (Without :on_associated, this would check for the :edit_post privilege on posts).
Other keywords add specific restrictions, as follows:
self.check_permission! :privilege
throwing the PermissionFailure exception if User.current does not have permission to perform the operation.
This may not work for private methods; that‘s not a bug, as this is intended to guard public APIs.
Note that attempting to set an attribute to the value it already has will not result in a permissions check. (There are points at which Rails does this internally, which would yield spurious permission failures if we did these checks).
require_privilege :edit_post, :to_update_attribute => :entry_txt
would require the :edit_post privilege only if someone is trying to update the text of an existing post, but would allow them to initialize the text of a new, unsaved one.
All of these work on attributes which shadow database columns, and those that don‘t (e.g., declared by attr_accessor). The only known bug is that if you‘re using composed_of, you should guard the composite pseudo-attribute, not the component attributes corresponding to individual database columns.
class BlogEntry
include Access::Controlled
belongs_to :blog
end
class Blog
include Access::Controlled
require_privilege :post,
:to_associate_as => ['BlogEntry#blog']
require_privilege :delete_post,
:to_dissociate_as => ['BlogEntry#blog']
end
This causes the following permission checks:
BlogEntry.new :blog => some_blog, ... BlogEntry.create :blog => some_blog, ... blog_entry.blog = some_blog blog_entry.blog_id = params[...][:blog_id]
:to_associate_as arranges that we first do some_blog.check_permission!( :post ), before actually changing the association. (For the blog_id case, this is actually implemented by a check within the database, to avoid the full expense of loading the object).
some_entry.blog = some_other_blog some_entry.update_attribute :blog, some_other_blog some_entry.destroy
:to_dissociate_as arranges that we first do some_blog.check_permission!( :delete_post ), before actually performing the operation.
These options are not compatible with :on_associated.
The lifecycle points are:
These are implemented by hanging permission checks on the +:before_create+, +:after_find+, +:before_update+, and +:before_destroy+ active record callbacks, using +require_privilege :at_callback+.
# File lib/access_require_privilege.rb, line 238
238: def require_privilege privilege, key_args
239:
240: declare_privilege privilege unless key_args[ :on_associated ]
241: require_privilege_internal privilege, key_args
242:
243: end